<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" >
  <link rel="dns-prefetch" href="https://mliyuanbiao.github.io">
  <title>我的个人博客</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="知识整理">
<meta property="og:type" content="website">
<meta property="og:title" content="我的个人博客">
<meta property="og:url" content="https://mliyuanbiao.github.io/index.html">
<meta property="og:site_name" content="我的个人博客">
<meta property="og:description" content="知识整理">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="我的个人博客">
<meta name="twitter:description" content="知识整理">
  
    <link rel="alternative" href="/atom.xml" title="我的个人博客" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  <link rel="stylesheet" type="text/css" href="/./main.0cf68a.css">
  <style type="text/css">
  
    #container.show {
      background: linear-gradient(200deg,#a0cfe4,#e8c37e);
    }
  </style>
  

  

</head>

<body>
  <div id="container" q-class="show:isCtnShow">
    <canvas id="anm-canvas" class="anm-canvas"></canvas>
    <div class="left-col" q-class="show:isShow">
      
<div class="overlay" style="background: #4d4d4d"></div>
<div class="intrude-less">
	<header id="header" class="inner">
		<a href="/" class="profilepic">
			<img src="/images/avatar.jpeg" class="js-avatar">
		</a>
		<hgroup>
		  <h1 class="header-author"><a href="/">liyuanbiao</a></h1>
		</hgroup>
		

		<nav class="header-menu">
			<ul>
			
				<li><a href="/">主页</a></li>
	        
				<li><a href="/tags/android/">Android</a></li>
	        
				<li><a href="/tags/java/">java</a></li>
	        
			</ul>
		</nav>
		<nav class="header-smart-menu">
    		
    			
    			<a q-on="click: openSlider(e, 'innerArchive')" href="javascript:void(0)">所有文章</a>
    			
            
    			
    			<a q-on="click: openSlider(e, 'friends')" href="javascript:void(0)">友链</a>
    			
            
    			
    			<a q-on="click: openSlider(e, 'aboutme')" href="javascript:void(0)">关于我</a>
    			
            
		</nav>
		<nav class="header-nav">
			<div class="social">
				
					<a class="weibo" target="_blank" href="#" title="weibo"><i class="icon-weibo"></i></a>
		        
					<a class="rss" target="_blank" href="#" title="rss"><i class="icon-rss"></i></a>
		        
					<a class="zhihu" target="_blank" href="#" title="zhihu"><i class="icon-zhihu"></i></a>
		        
					<a class="qq" target="_blank" href="#" title="qq"><i class="icon-qq"></i></a>
		        
			</div>
		</nav>
	</header>		
</div>

    </div>
    <div class="mid-col" q-class="show:isShow,hide:isShow|isFalse">
      
<nav id="mobile-nav">
  	<div class="overlay js-overlay" style="background: #4d4d4d"></div>
	<div class="btnctn js-mobile-btnctn">
  		<div class="slider-trigger list" q-on="click: openSlider(e)"><i class="icon icon-sort"></i></div>
	</div>
	<div class="intrude-less">
		<header id="header" class="inner">
			<div class="profilepic">
				<img src="/images/avatar.jpeg" class="js-avatar">
			</div>
			<hgroup>
			  <h1 class="header-author js-header-author">liyuanbiao</h1>
			</hgroup>
			
			
			
				
			
				
			
				
			
			
			
			<nav class="header-nav">
				<div class="social">
					
						<a class="weibo" target="_blank" href="#" title="weibo"><i class="icon-weibo"></i></a>
			        
						<a class="rss" target="_blank" href="#" title="rss"><i class="icon-rss"></i></a>
			        
						<a class="zhihu" target="_blank" href="#" title="zhihu"><i class="icon-zhihu"></i></a>
			        
						<a class="qq" target="_blank" href="#" title="qq"><i class="icon-qq"></i></a>
			        
				</div>
			</nav>

			<nav class="header-menu js-header-menu">
				<ul style="width: 70%">
				
				
					<li style="width: 33.333333333333336%"><a href="/">主页</a></li>
		        
					<li style="width: 33.333333333333336%"><a href="/tags/android/">Android</a></li>
		        
					<li style="width: 33.333333333333336%"><a href="/tags/java/">java</a></li>
		        
				</ul>
			</nav>
		</header>				
	</div>
	<div class="mobile-mask" style="display:none" q-show="isShow"></div>
</nav>

      <div id="wrapper" class="body-wrap">
        <div class="menu-l">
          <div class="canvas-wrap">
            <canvas data-colors="#eaeaea" data-sectionHeight="100" data-contentId="js-content" id="myCanvas1" class="anm-canvas"></canvas>
          </div>
          <div id="js-content" class="content-ll">
            
  
    <article id="post-关于-Kotlin-几篇文章的一些讨论" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/95d4684d.html">关于 Kotlin 几篇文章的一些讨论</a>
    </h1>
  

        
        <a href="/post/95d4684d.html" class="archive-article-date">
  	<time datetime="2018-06-26T05:25:29.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2018-06-26</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="抛弃-Java-改用-Kotlin-的六个月后，我后悔了"><a href="#抛弃-Java-改用-Kotlin-的六个月后，我后悔了" class="headerlink" title="抛弃 Java 改用 Kotlin 的六个月后，我后悔了"></a>抛弃 Java 改用 Kotlin 的六个月后，我后悔了</h2><p>原文地址：<a href="https://blog.csdn.net/csdnnews/article/details/80746096" target="_blank" rel="noopener">https://blog.csdn.net/csdnnews/article/details/80746096</a></p>
<p>这篇文章我很早之前之前就看过了，是一片批判 Kotlin 的文章，一时间在各大技术论坛疯狂转载。由于我本人很喜欢 Kotlin ，所以只是粗略的看了一眼，并觉得作者是在放屁。上面做法并不符合我认真求学的风格，今天我决定认真的看看，仔细分析下。</p>
<p>文中举了很多例子说明 Kotlin 的不完美，甚至会降低工作效率。我觉得，尽管 Kotlin 不完美，因为任何一门编程语言都不可能尽善尽美，但是他是在 Java 的基础上，为了解决 Java 的一些设计缺陷而设计出来的语言，使用 Kotlin 能极大增加工作效率。</p>
<h3 id="名称遮蔽"><a href="#名称遮蔽" class="headerlink" title="名称遮蔽"></a>名称遮蔽</h3><p>作者认为这是 Koltin 的设计缺陷，试图通过警告信息来解决这个问题。我认为这是 Kotlin 的一个功能，并不属于缺陷，名称遮蔽并不会造成编程错误，在有些地方也有名称遮蔽的应用。</p>
<h3 id="类型推断"><a href="#类型推断" class="headerlink" title="类型推断"></a>类型推断</h3><p>Kotlin 的类型推断比 Java 强大不止一点点，Java 在 8 开始才对类型推断有比较好的支持，到了 10 才可以使用<code>var</code>来声明变量，通过类型推断而不需要显示声明变量类型。这一特性也只在局部作用域生效。现在<br>Java 的使用者里面有很多都是Android开发者，本人就是。Android 对 Java 新语法的支持并不是很良好，为了兼容以前的 Android 系统，开发者们不得不使用 Java7 甚至 Java6 的语法。Kotlin 的出现让这一切有了很大的改观。</p>
<h3 id="Null-安全类型"><a href="#Null-安全类型" class="headerlink" title="Null 安全类型"></a>Null 安全类型</h3><p>作者在描述 Kotlin 与Java 互操作的过程中不可避免的会产生第三种类型，<strong>平台类型</strong>，作者认为平台类型会让代表变得糟糕，误导你，会禁用 Kotlin 的 NULL 安全机制。</p>
<p>我觉得 Kotlin 为了要与 Java 实现100% 互操作而产生平台类型这是不可避免的，因为 Java 从一开始就没有这种安全机制，在 Java 语言中，每个类变量都有可能为 null 。正是因为这样，Kotlin 编译器才为了我们做了更多的事情，他并没有将所有的 Java 变量翻译成可空类型。</p>
<ul>
<li>基础类型是不可能为 null 的，在 Kotlin 中会作为不可空类型的存在</li>
<li>Kotlin编译器还支持识别<code>@Nullable</code> 和 <code>@NonNull</code> 注解，他们分别会被翻译为可空类型和非空类型，现在很多类库为了适应 Kotlin 而加上了这些注解。</li>
<li>当平台类型不可避免的时候，可以显示为 Java 变量声明可空类型或者非空类型，避免在 Kotlin 中使用平台类型。如果你没有遵守这一规则，Kotlin 编译器则会以警告的形式提示你。</li>
<li>Kotlin 有强大的 IDE 支持，在编辑器中会显示类型推断的实际类型。平台类型则是类型名称后面加上！。这只在编辑器中作为提示显示，语法中并不存在。</li>
</ul>
<p>Kotlin 并不会误导你，对语言的不熟悉才是你被误导的真正原因。我觉得 Kotlin有很多吸引我的地方，其中 Null 安全机制是我最喜欢的。</p>
<h3 id="Maybe"><a href="#Maybe" class="headerlink" title="Maybe"></a>Maybe</h3><p>在 Java8 中为了解决空安全的问题，而引入了Optional。作者称 如今，Optional 是在 API 边界处理返回类型中的空值的非常流行的方式。而我倒觉得这是非常无奈的做法，比起 Kotlin 从根源上解决查太多。</p>
<p>为了论证 Kotlin 很差，作者举了下面的例子：<br>例如，在 Java 中： </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">parseAndInc</span><span class="params">(String number)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Optional.ofNullable(number)</span><br><span class="line">                   .map(Integer::parseInt)</span><br><span class="line">                   .map(it -&gt; it + <span class="number">1</span>)</span><br><span class="line">                   .orElse(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在 Kotlin 中，需要这样做</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">parseAndInc</span><span class="params">(number: <span class="type">String</span>?)</span></span>: <span class="built_in">Int</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> number?.let &#123; Integer.parseInt(it) &#125;</span><br><span class="line">                 ?.let &#123; it -&gt; it + <span class="number">1</span> &#125; ?: <span class="number">0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后比较这两段代码的可读性。</p>
<p>可读性对于每个人都不一样吧，而且谁会在 kotlin 使用 <code>Integer.parseInt</code> 呢？如果作者喜欢 Optional ，你可以使用它。 Kotlin 在 JVM 上运行。即使是自己实现一个类似于 Optional 的类也是很容易的。</p>
<h3 id="类名称字面常量"><a href="#类名称字面常量" class="headerlink" title="类名称字面常量"></a>类名称字面常量</h3><p>这点我就不想多说了，作者纯粹为了找茬嘛。<br>文中说：Kotlin 把 Kotlin 类和 Java 类进行了区分，并为其提供了语法规范：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> kotlinClass : KClass&lt;LocalDate&gt; = LocalDate::<span class="class"><span class="keyword">class</span></span></span><br><span class="line"><span class="keyword">val</span> javaClass : Class&lt;LocalDate&gt; = LocalDate::<span class="class"><span class="keyword">class</span>.<span class="title">java</span></span></span><br></pre></td></tr></table></figure>
<p>这看起来非常丑陋。</p>
<p>丑陋，呵呵呵呵呵呵呵。。。。。<br>还有其他的一些点论证 Kotlin 无比丑陋的说法就不一一说明了。</p>
<p>另外作为认为 Kotlin 具有陡峭的学习路线，这点我是不认同的，我只用了两天就将 Kotlin 用于生产环境了。Groovy 虽然很容易从 Java 转换过来，因为Java代码是正确的 Groovy 代码，但是想要深入学习一点都不比 Kotlin 容易，特别是没有接触过弱类型语言的开发者来说。Groovy 我也学习过，我更认为同样作为静态类型的 Kotlin 与 Java 更接近，即使他的语法与 Scala 接近。</p>
<h3 id="关于作者最后的想法"><a href="#关于作者最后的想法" class="headerlink" title="关于作者最后的想法"></a>关于作者最后的想法</h3><blockquote>
<p>学习新技术就像一项投资。我们投入时间，新技术让我们得到回报。但我并不是说 Kotlin 是一种糟糕的语言，只是在我们的案例中，成本远超收益。</p>
</blockquote>
<p>作者说的是普遍的道理，所以我不能说他错，但是就目前而言我不认为 Kotlin 是糟糕的语言。使用 Kotlin 一年多时间以来，我感受到 Kotlin 带来的诸多好处，一种学习新语言，新特性带来的心灵洗礼，我不会说 Java 和 Kotlin 谁更好，他们都很好，我都很喜欢。</p>
<h2 id="谷歌大牛说：为什么-Kotlin-比你们用的那些垃圾语言都好"><a href="#谷歌大牛说：为什么-Kotlin-比你们用的那些垃圾语言都好" class="headerlink" title="谷歌大牛说：为什么 Kotlin 比你们用的那些垃圾语言都好"></a>谷歌大牛说：为什么 Kotlin 比你们用的那些垃圾语言都好</h2><p>原文地址：<a href="http://blog.jobbole.com/111249/" target="_blank" rel="noopener">http://blog.jobbole.com/111249/</a></p>
<p>这篇文章在安卓团队在谷歌 I/O 2017 大会上宣布 Kotlin 成为官方头等支持语言之后发布，同样获得疯狂的转载。这篇文章的标题有点标题党，甚至有点引战的味道。要让我用一个词来形容就是跪舔。</p>
<p>作者讲述了十年来的经历，以及为什么使用 Kotlin 。<br>比如 Android 有几个很糟糕的 API。因为这样，接下来一年半没有碰 Android 编程。来自俄罗斯的救星 Kotlin，对他第一印象是简洁，而且有一种似曾相识的感觉，和 Swift 很像，然后简述 Kotlin 和 Swift 的历史，是 Kotlin 在前，Swift 在后，所以是 Swift 和 Kotlin 很像。<br>其实谁和谁像已经无所谓了， Kotlin 和 Swift 都是现代化语言，而现在化语言都是很像的，作者花了大篇幅只为争个先后，这是跪舔。</p>
<p>接下里说为什么要从 Java 叛逃到 Kotlin。大部分是讲和 Java8 的对比，还有在 Android开发上的便利性，这和标题有个毛关系。回到糟糕的Android API，原文说：</p>
<blockquote>
<p>Kotlin 致力于让大家绕过 Android API 那些恶心的东西，并且能让你充分发挥你的经验，这一点甚至比 iOS 做得还要好。好吧，至少来说比 Objective-C 做得好，因为我觉得 Swift 肯定也不会差。知道为什么吗？因为 Swift 和 Kotlin 很像啊。</p>
</blockquote>
<p>Swift 和 Objective-C 只是在互操作性没有 Kotlin 与 Java 这么好，没办法，他们都是基础JVM的（我知道有人要杠了），这个 Android API 本身没有关系吧，API就在哪里，你用与不用，他都在哪里，请问 Kotlin 是如何绕过去的。 可能作者根本不喜欢 Android 吧，不然怎么会因为 Java 拒绝 Android，语句中看不出丝毫热爱。可能是为了跪舔而扯上的Android吧，但似乎有点用力过猛了。这也是跪舔。</p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color2">Kotlin</a>
        		</li>
      		
		</ul>
	</div>

      

      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/95d4684d.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-让Java类在kotlin中也支持解构声明" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/dced847d.html">让Java类在kotlin中也支持解构声明</a>
    </h1>
  

        
        <a href="/post/dced847d.html" class="archive-article-date">
  	<time datetime="2018-02-08T03:34:57.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2018-02-08</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>关于<code>kotlin</code>的一大段介绍我就不说了，首先我们来看看结构声明在<code>kotlin</code>中是如何使用的。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//首先定义一个Person类</span></span><br><span class="line"><span class="keyword">data</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span></span>(<span class="keyword">val</span> name: String? = <span class="literal">null</span>, <span class="keyword">val</span> age: <span class="built_in">Int</span> = <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span>&lt;<span class="type">String</span>&gt;)</span></span> &#123;</span><br><span class="line">    <span class="keyword">val</span> (name, age) = Person(<span class="string">"zhangsan"</span>, <span class="number">20</span>)</span><br><span class="line">    println(name) <span class="comment">// zhangsan</span></span><br><span class="line">    println(age) <span class="comment">// 20</span></span><br><span class="line"><span class="comment">// ===等价于===</span></span><br><span class="line">    <span class="keyword">val</span> p = Person(<span class="string">"zhangsan"</span>, <span class="number">20</span>)</span><br><span class="line">    println(p.name) <span class="comment">// zhangsan</span></span><br><span class="line">    println(p.age) <span class="comment">// 20</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>有时把一个对象 解构 成很多变量会很方便，我们可以同时声明多个变量<br>一个解构声明会被编译成以下代码：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> name = p.component1() <span class="comment">// zhangsan</span></span><br><span class="line"><span class="keyword">val</span> age = p.component2() <span class="comment">// 20</span></span><br></pre></td></tr></table></figure>
<p>其中<code>component1()</code>,<code>component2()</code>,<code>componentN()</code>函数是由Kotlin编译器自动生成的，方法的数量取决于属性的数量，方法的顺序取决于属性的顺序。</p>
<p>我们用Kotlin插件的<code>Show Kotlin Bytecode</code>功能将<code>Person</code>反编译可以得到如下Java代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>&#123;</span><br><span class="line">   <span class="meta">@Nullable</span> <span class="comment">// String?类型隐含添加上@Nullable注解，在Java调用Kotlin时能给出友好地非空提示</span></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">final</span> String name;</span><br><span class="line">   <span class="comment">// Int类型会被翻译成基本类型int，只有需要用到方法时才会转成Integer</span></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> age;</span><br><span class="line"></span><br><span class="line">   ... setter/getter ... </span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">Person</span><span class="params">(@Nullable String name, <span class="keyword">int</span> age)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">this</span>.name = name;</span><br><span class="line">      <span class="keyword">this</span>.age = age;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   ......</span><br><span class="line">   </span><br><span class="line">   <span class="meta">@Nullable</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">component1</span><span class="params">()</span> </span>&#123; <span class="comment">// 第一个属性</span></span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">component2</span><span class="params">()</span> </span>&#123; <span class="comment">// 第二个属性</span></span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.age;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@NotNull</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> Person <span class="title">copy</span><span class="params">(@Nullable String name, <span class="keyword">int</span> age)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> Person(name, age);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   ... toString/hashCode/equals ... </span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>从上面的代码可以看出，<code>data class</code>除了自动生成<code>setter/getter</code>，<code>toString</code>，<code>equals</code>，<code>hashCode</code>之外，还有<code>component1</code>和<code>component2</code>。但是<code>Java</code>编译器是不会自动生成的，也就无法使用结构声明。</p>
<p>我们可以手动添加吗？？？？ 答案是可以的，你可以在<code>Person</code>类(Java)里面添加如下代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@NotNull</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">component1</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> getName();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">component2</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> getAge();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面的方法可行，但似乎不够完美，我们大部分使用的API都是无法更改其内部结构的。根本没办法手动添加。</p>
<p>其实<code>Kotlin</code>是支持结构声明扩展的，你可以这么做</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. operator关键字必不可少</span></span><br><span class="line"><span class="comment">// 2. 为什么我这里显示声明**String?**呢？</span></span><br><span class="line"><span class="comment">// Person是Java类，getName()返回的是Java的平台类型(String!),而不是String或者String?，可空性未知</span></span><br><span class="line"><span class="comment">// 你应当显示声明name是否可空，以便更好运用Kotlin空安全的特性。</span></span><br><span class="line"><span class="comment">// 当然你也可以不添加，因此你会得到一个波浪线和一个黄色感叹号，但是这并不会影响程序的运行</span></span><br><span class="line"><span class="comment">// 而int类型是基本类型，编译成Int类型是一定非空的，开发工具不会因此报出警告。</span></span><br><span class="line"><span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> Person.<span class="title">component1</span><span class="params">()</span></span>: String? = name </span><br><span class="line"><span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> Person.<span class="title">component2</span><span class="params">()</span></span> = age</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span>&lt;<span class="type">String</span>&gt;)</span></span> &#123;</span><br><span class="line">    <span class="keyword">val</span> (name, age) = Person(<span class="string">"zhangsan"</span>, <span class="number">20</span>)</span><br><span class="line">    println(name)</span><br><span class="line">    println(age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>讲到这里，我想同学们都很清楚知道如何使用结构声明扩展让Java类也支持结构声明了，真是可以可贺。</p>
<p>你可能见过下面一段代码：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> ((key, value) <span class="keyword">in</span> map) &#123;</span><br><span class="line"><span class="comment">// 使用该 key、value 做些事情</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这是一段遍历map的方法，实现它我们只需要这样做</p>
<ul>
<li>通过提供一个 <code>iterator()</code> 函数将映射表示为一个值的序列；</li>
<li>通过提供函数component1() 和 component2() 来将每个元素呈现为一对。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">operator fun &lt;K, V&gt; Map&lt;K, V&gt;.iterator(): Iterator&lt;Map.Entry&lt;K, V&gt;&gt; = entrySet().itera</span><br><span class="line">tor()</span><br><span class="line">operator fun &lt;K, V&gt; Map.Entry&lt;K, V&gt;.component1() = getKey()</span><br><span class="line">operator fun &lt;K, V&gt; Map.Entry&lt;K, V&gt;.component2() = getValue()</span><br></pre></td></tr></table></figure>
<p>题外话：其实kotlin标准库已经实现了一些很方便的扩展，包括上述的。<code>kotlin</code>拥有<code>java</code>所不具有的诸多优点，在谷歌大力支持下，我觉得Android开发者都应该学习并使用这门语言。</p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color5">Java</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color2">Kotlin</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/dced847d.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-kotlin编译问题01" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/1f2f4db2.html">kotlin编译 Kotlin home does not exist or is not a directory</a>
    </h1>
  

        
        <a href="/post/1f2f4db2.html" class="archive-article-date">
  	<time datetime="2018-02-03T19:19:36.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2018-02-04</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>虽然kotlin成为谷歌干儿子才半年之久，但是我依然早早的在项目中使用它。秉承着早入坑早脱坑的理念，我遇到了下面这个问题。代码没有报错，但是编译不通过。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">e: Kotlin home does not exist or is not a directory:</span><br></pre></td></tr></table></figure>
<p>这个问题原因不明，莫名其妙出现的。代码完全没有问题，但是就是不能编译。<br>下面是解决方法</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">killall java</span><br><span class="line">./gradlew clean assemble</span><br></pre></td></tr></table></figure>
<p>解决原理不清楚。</p>
<p>下面是相关连接<br><a href="https://discuss.kotlinlang.org/t/task-compilekotlin2js-fails-in-gradle-4-4-kotlin-home-does-not-exist-or-is-not-a-directory/5706/3" target="_blank" rel="noopener">https://discuss.kotlinlang.org/t/task-compilekotlin2js-fails-in-gradle-4-4-kotlin-home-does-not-exist-or-is-not-a-directory/5706/3</a></p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color2">Kotlin</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/1f2f4db2.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-Android蓝牙" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/639eaf27.html">Android蓝牙</a>
    </h1>
  

        
        <a href="/post/639eaf27.html" class="archive-article-date">
  	<time datetime="2017-12-21T07:15:57.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2017-12-21</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>Android 平台包含蓝牙网络堆栈支持，凭借此项支持，设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。 这些 API 允许应用以无线方式连接到其他蓝牙设备，从而实现点到点和多点无线功能。</p>
<ul>
<li>使用 Bluetooth API，Android 应用可执行以下操作：</li>
<li>扫描其他蓝牙设备</li>
<li>查询本地蓝牙适配器的配对蓝牙设备</li>
<li>建立 RFCOMM 通道</li>
<li>通过服务发现连接到其他设备</li>
<li>与其他设备进行双向数据传输</li>
<li>管理多个连接</li>
</ul>
<p>本文将介绍如何使用传统蓝牙。传统蓝牙适用于电池使用强度较大的操作，例如 Android 设备之间的流式传输和通信等。 针对具有低功耗要求的蓝牙设备，Android 4.3（API 级别 18）中引入了面向低功耗蓝牙的 API 支持。 如需了解更多信息，请参阅<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html" target="_blank" rel="noopener">低功耗蓝牙</a>。</p>
<h1 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h1><p>本文将介绍如何使用 Android Bluetooth API 来完成使用蓝牙进行通信的四项主要任务：设置蓝牙、查找局部区域内的配对设备或可用设备、连接设备，以及在设备之间传输数据。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/package-summary.html" target="_blank" rel="noopener">android.bluetooth</a> 包中提供了所有 Bluetooth API。 下面概要列出了创建蓝牙连接所需的类和接口：</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html" target="_blank" rel="noopener">BluetoothAdapter</a><br>表示本地蓝牙适配器（蓝牙无线装置）。 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html" target="_blank" rel="noopener">BluetoothAdapter</a> 是所有蓝牙交互的入口点。 利用它可以发现其他蓝牙设备，查询绑定（配对）设备的列表，使用已知的 MAC 地址实例化 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html" target="_blank" rel="noopener">BluetoothDevice</a>，以及创建 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 以侦听来自其他设备的通信。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html" target="_blank" rel="noopener">BluetoothDevice</a><br>表示远程蓝牙设备。利用它可以通过 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 请求与某个远程设备建立连接，或查询有关该设备的信息，例如设备的名称、地址、类和绑定状态等。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a><br>表示蓝牙套接字接口（与 TCP Socket 相似）。这是允许应用通过 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a><br>表示用于侦听传入请求的开放服务器套接字（类似于 TCP <a href="https://developer.android.google.cn/reference/java/net/ServerSocket.html" target="_blank" rel="noopener">ServerSocket</a>）。 要连接两台 Android 设备，其中一台设备必须使用此类开放一个服务器套接字。 当一台远程蓝牙设备向此设备发出连接请求时， <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 将会在接受连接后返回已连接的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothClass.html" target="_blank" rel="noopener">BluetoothClass</a><br>描述蓝牙设备的一般特征和功能。 这是一组只读属性，用于定义设备的主要和次要设备类及其服务。 不过，它不能可靠地描述设备支持的所有蓝牙配置文件和服务，而是适合作为设备类型提示。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.html" target="_blank" rel="noopener">BluetoothProfile</a><br>表示蓝牙配置文件的接口。 蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。 免提配置文件便是一个示例。 如需了解有关配置文件的详细讨论，请参阅使用配置文件</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHeadset.html" target="_blank" rel="noopener">BluetoothHeadset</a><br>提供蓝牙耳机支持，以便与手机配合使用。 其中包括蓝牙耳机和免提（1.5 版）配置文件。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothA2dp.html" target="_blank" rel="noopener">BluetoothA2dp</a><br>定义高质量音频如何通过蓝牙连接和流式传输，从一台设备传输到另一台设备。“A2DP”代表高级音频分发配置文件。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealth.html" target="_blank" rel="noopener">BluetoothHealth</a><br>表示用于控制蓝牙服务的健康设备配置文件代理。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealthCallback.html" target="_blank" rel="noopener">BluetoothHealthCallback</a><br>用于实现 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealth.html" target="_blank" rel="noopener">BluetoothHealth</a> 回调的抽象类。您必须扩展此类并实现回调方法，以接收关于应用注册状态和蓝牙通道状态变化的更新内容。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealthAppConfiguration.html" target="_blank" rel="noopener">BluetoothHealthAppConfiguration</a><br>表示第三方蓝牙健康应用注册的应用配置，以便与远程蓝牙健康设备通信。</p>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.ServiceListener.html" target="_blank" rel="noopener">BluetoothProfile.ServiceListener</a><br>在 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.html" target="_blank" rel="noopener">BluetoothProfile</a> IPC 客户端连接到服务（即，运行特定配置文件的内部服务）或断开服务连接时向其发送通知的接口。</p>
<h1 id="蓝牙权限"><a href="#蓝牙权限" class="headerlink" title="蓝牙权限"></a>蓝牙权限</h1><p>要在应用中使用蓝牙功能，必须声明蓝牙权限 <a href="https://developer.android.google.cn/reference/android/Manifest.permission.html#BLUETOOTH" target="_blank" rel="noopener">BLUETOOTH</a>。您需要此权限才能执行任何蓝牙通信，例如请求连接、接受连接和传输数据等。</p>
<p>如果您希望您的应用启动设备发现或操作蓝牙设置，则还必须声明 <a href="https://developer.android.google.cn/reference/android/Manifest.permission.html#BLUETOOTH_ADMIN" target="_blank" rel="noopener">BLUETOOTH_ADMIN</a> 权限。 大多数应用需要此权限仅仅为了能够发现本地蓝牙设备。 除非该应用是将要应用户请求修改蓝牙设置的“超级管理员”，否则不应使用此权限所授予的其他能力。 注：如果要使用 <a href="https://developer.android.google.cn/reference/android/Manifest.permission.html#BLUETOOTH_ADMIN" target="_blank" rel="noopener">BLUETOOTH_ADMIN</a> 权限，则还必须拥有 <a href="https://developer.android.google.cn/reference/android/Manifest.permission.html#BLUETOOTH" target="_blank" rel="noopener">BLUETOOTH</a> 权限。</p>
<p>在您的应用清单文件中声明蓝牙权限。例如：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">manifest</span> <span class="attr">...</span> &gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.BLUETOOTH"</span> /&gt;</span></span><br><span class="line">  ...</span><br><span class="line"><span class="tag">&lt;/<span class="name">manifest</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>如需了解有关声明应用权限的更多信息，请参阅 <a href="https://developer.android.google.cn/guide/topics/manifest/uses-permission-element.html" target="_blank" rel="noopener"><uses-permission></uses-permission></a> 参考资料。</p>
<h1 id="设置蓝牙"><a href="#设置蓝牙" class="headerlink" title="设置蓝牙"></a>设置蓝牙</h1><p><img src="/media/15138410782966.png" alt=""><br>图 1：启用蓝牙对话框。</p>
<p>您需要验证设备支持蓝牙，并且如果支持确保将其启用，然后您的应用才能通过蓝牙进行通信。</p>
<p>如果不支持蓝牙，则应正常停用任何蓝牙功能。 如果支持蓝牙但已停用此功能，则可以请求用户在不离开应用的同时启用蓝牙。 可使用 BluetoothAdapter，分两步完成此设置：</p>
<ol>
<li><p>获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html" target="_blank" rel="noopener">BluetoothAdapter</a></p>
<p> 所有蓝牙 Activity 都需要 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html" target="_blank" rel="noopener">BluetoothAdapter</a>。 要获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html" target="_blank" rel="noopener">BluetoothAdapter</a>，请调用静态 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#getDefaultAdapter(" target="_blank" rel="noopener">getDefaultAdapter()</a>) 方法。这将返回一个表示设备自身的蓝牙适配器（蓝牙无线装置）的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html" target="_blank" rel="noopener">BluetoothAdapter</a>。 整个系统有一个蓝牙适配器，并且您的应用可使用此对象与之交互。 如果 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#getDefaultAdapter(" target="_blank" rel="noopener">getDefaultAdapter()</a>) 返回 null，则该设备不支持蓝牙，您的操作到此为止。 例如： </p>
</li>
</ol>
<pre><code><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();</span><br><span class="line"><span class="keyword">if</span> (mBluetoothAdapter == <span class="keyword">null</span>) &#123;</span><br><span class="line">    <span class="comment">// Device does not support Bluetooth</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</code></pre><ol>
<li><p>启动蓝牙</p>
<p>下一步，您需要确保已启用蓝牙。调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#isEnabled(" target="_blank" rel="noopener">isEnabled()</a>) 以检查当前是否已启用蓝牙。 如果此方法返回 false，则表示蓝牙处于停用状态。要请求启用蓝牙，请使用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#ACTION_REQUEST_ENABLE" target="_blank" rel="noopener">ACTION_REQUEST_ENABLE</a> 操作 Intent 调用 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int" target="_blank" rel="noopener">startActivityForResult()</a>)。这将通过系统设置发出启用蓝牙的请求（无需停止您的应用）。 例如：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!mBluetoothAdapter.isEnabled()) &#123;</span><br><span class="line">    Intent enableBtIntent = <span class="keyword">new</span> Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);</span><br><span class="line">    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如图 1 所示，将显示对话框，请求用户允许启用蓝牙。如果用户响应“Yes”，系统将开始启用蓝牙，并在该进程完成（或失败）后将焦点返回到您的应用。</p>
<p>传递给 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int" target="_blank" rel="noopener">startActivityForResult()</a>) 的 REQUEST_ENABLE_BT 常量是在局部定义的整型（必须大于 0），系统会将其作为 requestCode 参数传递回您的 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent" target="_blank" rel="noopener">onActivityResult()</a>) 实现。</p>
<p>如果成功启用蓝牙，您的 Activity 将会在 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent" target="_blank" rel="noopener">onActivityResult()</a>) 回调中收到 RESULT_OK 结果代码。 如果由于某个错误（或用户响应“No”）而没有启用蓝牙，则结果代码为 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#RESULT_CANCELED" target="_blank" rel="noopener">RESULT_CANCELED</a>。</p>
</li>
</ol>
<p>您的应用还可以选择侦听 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#ACTION_STATE_CHANGED" target="_blank" rel="noopener">ACTION_STATE_CHANGED</a> 广播 Intent，每当蓝牙状态发生变化时，系统都会广播此 Intent。 此广播包含额外字段 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#EXTRA_STATE" target="_blank" rel="noopener">EXTRA_STATE</a> 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#EXTRA_PREVIOUS_STATE" target="_blank" rel="noopener">EXTRA_PREVIOUS_STATE</a>，二者分别包含新的和旧的蓝牙状态。 这些额外字段可能的值包括 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#STATE_TURNING_ON" target="_blank" rel="noopener">STATE_TURNING_ON</a>、<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#STATE_ON" target="_blank" rel="noopener">STATE_ON</a>、<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#STATE_TURNING_OFF" target="_blank" rel="noopener">STATE_TURNING_OFF</a> 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#STATE_OFF" target="_blank" rel="noopener">STATE_OFF</a>。侦听此广播适用于检测在您的应用运行期间对蓝牙状态所做的更改。</p>
<blockquote>
<p><strong>提示</strong>：启用可检测性将会自动启用蓝牙。 如果您计划在执行蓝牙 Activity 之前一直启用设备的可检测性，则可以跳过上述步骤 2。 阅读下面的<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#EnablingDiscoverability" target="_blank" rel="noopener">启用可检测性</a>。</p>
</blockquote>
<h1 id="查找设备"><a href="#查找设备" class="headerlink" title="查找设备"></a>查找设备</h1><p>使用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html" target="_blank" rel="noopener">BluetoothAdapter</a>，您可以通过设备发现或通过查询配对（绑定）设备的列表来查找远程蓝牙设备。</p>
<p>设备发现是一个扫描过程，它会搜索局部区域内已启用蓝牙功能的设备，然后请求一些关于各台设备的信息（有时也被称为“发现”、“查询”或“扫描”）。但局部区域内的蓝牙设备仅在其当前已启用可检测性时才会响应发现请求。 如果设备可检测到，它将通过共享一些信息（例如设备名称、类及其唯一 MAC 地址）来响应发现请求。 利用此信息，执行发现的设备可以选择发起到被发现设备的连接。</p>
<p>在首次与远程设备建立连接后，将会自动向用户显示配对请求。 设备完成配对后，将会保存关于该设备的基本信息（例如设备名称、类和 MAC 地址），并且可使用 Bluetooth API 读取这些信息。 利用远程设备的已知 MAC 地址可随时向其发起连接，而无需执行发现操作（假定该设备处于有效范围内）。</p>
<p>请记住，被配对与被连接之间存在差别。被配对意味着两台设备知晓彼此的存在，具有可用于身份验证的共享链路密钥，并且能够与彼此建立加密连接。 被连接意味着设备当前共享一个 RFCOMM 通道，并且能够向彼此传输数据。 当前的 Android Bluetooth API 要求对设备进行配对，然后才能建立 RFCOMM 连接。 （在使用 Bluetooth API 发起加密连接时，会自动执行配对）。</p>
<p>以下几节介绍如何查找已配对的设备，或使用设备发现来发现新设备。</p>
<blockquote>
<p><strong>注</strong>：Android 设备默认处于不可检测到状态。 用户可通过系统设置将设备设为在有限的时间内处于可检测到状态，或者，应用可请求用户在不离开应用的同时启用可检测性。 下面讨论如何<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#EnablingDiscoverability" target="_blank" rel="noopener">启用可检测性</a>。</p>
</blockquote>
<h1 id="查询配对的设备"><a href="#查询配对的设备" class="headerlink" title="查询配对的设备"></a>查询配对的设备</h1><p>在执行设备发现之前，有必要查询已配对的设备集，以了解所需的设备是否处于已知状态。 为此，请调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#getBondedDevices(" target="_blank" rel="noopener">getBondedDevices()</a>)。 这将返回表示已配对设备的一组 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html" target="_blank" rel="noopener">BluetoothDevice</a>。 例如，您可以查询所有已配对设备，然后使用 ArrayAdapter 向用户显示每台设备的名称：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Set&lt;BluetoothDevice&gt; pairedDevices = mBluetoothAdapter.getBondedDevices();</span><br><span class="line"><span class="comment">// If there are paired devices</span></span><br><span class="line"><span class="keyword">if</span> (pairedDevices.size() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// Loop through paired devices</span></span><br><span class="line">    <span class="keyword">for</span> (BluetoothDevice device : pairedDevices) &#123;</span><br><span class="line">        <span class="comment">// Add the name and address to an array adapter to show in a ListView</span></span><br><span class="line">        mArrayAdapter.add(device.getName() + <span class="string">"\n"</span> + device.getAddress());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>要发起连接，<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html" target="_blank" rel="noopener">BluetoothDevice</a> 对象仅仅需要提供 MAC 地址。 在此示例中，它将保存为显示给用户的 ArrayAdapter 的一部分。 之后可提取该 MAC 地址，以便发起连接。 您可以在有关<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#ConnectingDevices" target="_blank" rel="noopener">连接设备</a>的部分详细了解如何创建连接。</p>
<h1 id="发现设备"><a href="#发现设备" class="headerlink" title="发现设备"></a>发现设备</h1><p>要开始发现设备，只需调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery(" target="_blank" rel="noopener">startDiscovery()</a>)。该进程为异步进程，并且该方法会立即返回一个布尔值，指示是否已成功启动发现操作。 发现进程通常包含约 12 秒钟的查询扫描，之后对每台发现的设备进行页面扫描，以检索其蓝牙名称。</p>
<p>您的应用必须针对 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html#ACTION_FOUND" target="_blank" rel="noopener">ACTION_FOUND</a> Intent 注册一个 BroadcastReceiver，以便接收每台发现的设备的相关信息。 针对每台设备，系统将会广播 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html#ACTION_FOUND" target="_blank" rel="noopener">ACTION_FOUND</a> Intent。此 Intent 将携带额外字段 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html#EXTRA_DEVICE" target="_blank" rel="noopener">EXTRA_DEVICE</a> 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html#EXTRA_CLASS" target="_blank" rel="noopener">EXTRA_CLASS</a>，二者分别包含 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html" target="_blank" rel="noopener">BluetoothDevice</a> 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothClass.html" target="_blank" rel="noopener">BluetoothClass</a>。 例如，下面说明了在发现设备时如何注册以处理广播。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Create a BroadcastReceiver for ACTION_FOUND</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> BroadcastReceiver mReceiver = <span class="keyword">new</span> BroadcastReceiver() &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceive</span><span class="params">(Context context, Intent intent)</span> </span>&#123;</span><br><span class="line">        String action = intent.getAction();</span><br><span class="line">        <span class="comment">// When discovery finds a device</span></span><br><span class="line">        <span class="keyword">if</span> (BluetoothDevice.ACTION_FOUND.equals(action)) &#123;</span><br><span class="line">            <span class="comment">// Get the BluetoothDevice object from the Intent</span></span><br><span class="line">            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);</span><br><span class="line">            <span class="comment">// Add the name and address to an array adapter to show in a ListView</span></span><br><span class="line">            mArrayAdapter.add(device.getName() + <span class="string">"\n"</span> + device.getAddress());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// Register the BroadcastReceiver</span></span><br><span class="line">IntentFilter filter = <span class="keyword">new</span> IntentFilter(BluetoothDevice.ACTION_FOUND);</span><br><span class="line">registerReceiver(mReceiver, filter); <span class="comment">// Don't forget to unregister during onDestroy</span></span><br></pre></td></tr></table></figure>
<p>要发起连接，<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html" target="_blank" rel="noopener">BluetoothDevice</a> 对象仅仅需要提供 MAC 地址。 在此示例中，它将保存为显示给用户的 ArrayAdapter 的一部分。 之后可提取该 MAC 地址，以便发起连接。 您可以在有关<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#ConnectingDevices" target="_blank" rel="noopener">连接设备</a>的部分详细了解如何创建连接。</p>
<blockquote>
<p><strong>注意</strong>：执行设备发现对于蓝牙适配器而言是一个非常繁重的操作过程，并且会消耗大量资源。 在找到要连接的设备后，确保始终使用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery(" target="_blank" rel="noopener">cancelDiscovery()</a>) 停止发现，然后再尝试连接。 此外，如果您已经保持与某台设备的连接，那么执行发现操作可能会大幅减少可用于该连接的带宽，因此不应该在处于连接状态时执行发现操作。</p>
</blockquote>
<h1 id="启用可检测性"><a href="#启用可检测性" class="headerlink" title="启用可检测性"></a>启用可检测性</h1><p>如果您希望将本地设备设为可被其他设备检测到，请使用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#ACTION_REQUEST_DISCOVERABLE" target="_blank" rel="noopener">ACTION_REQUEST_DISCOVERABLE</a> 操作 Intent 调用 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int" target="_blank" rel="noopener">startActivityForResult(Intent, int)</a>)。 这将通过系统设置发出启用可检测到模式的请求（无需停止您的应用）。 默认情况下，设备将变为可检测到并持续 120 秒钟。 您可以通过添加 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#EXTRA_DISCOVERABLE_DURATION" target="_blank" rel="noopener">EXTRA_DISCOVERABLE_DURATION</a> Intent Extra 来定义不同的持续时间。 应用可以设置的最大持续时间为 3600 秒，值为 0 则表示设备始终可检测到。 任何小于 0 或大于 3600 的值都会自动设为 120 秒。 例如，以下片段会将持续时间设为 300 秒：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Intent discoverableIntent = <span class="keyword">new</span></span><br><span class="line">Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);</span><br><span class="line">discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, <span class="number">300</span>);</span><br><span class="line">startActivity(discoverableIntent);</span><br></pre></td></tr></table></figure>
<p><img src="/media/15138419867174.png" alt=""><br>图 2：启用可检测性对话框。</p>
<p>如图 2 所示，将显示对话框，请求用户允许将设备设为可检测到。如果用户响应“Yes”，则设备将变为可检测到并持续指定的时间量。 然后，您的 Activity 将会收到对 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent" target="_blank" rel="noopener">onActivityResult</a>)() 回调的调用，其结果代码等于设备可检测到的持续时间。 如果用户响应“No”或出现错误，结果代码将为 <a href="https://developer.android.google.cn/reference/android/app/Activity.html#RESULT_CANCELED" target="_blank" rel="noopener">RESULT_CANCELED</a>。</p>
<blockquote>
<p><strong>注</strong>：如果尚未在设备上启用蓝牙，则启用设备可检测性将会自动启用蓝牙。</p>
</blockquote>
<p>设备将在分配的时间内以静默方式保持可检测到模式。如果您希望在可检测到模式发生变化时收到通知，您可以针对 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#ACTION_SCAN_MODE_CHANGED" target="_blank" rel="noopener">ACTION_SCAN_MODE_CHANGED</a> Intent 注册 BroadcastReceiver。 它将包含额外字段 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#EXTRA_SCAN_MODE" target="_blank" rel="noopener">EXTRA_SCAN_MODE</a> 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#EXTRA_PREVIOUS_SCAN_MODE" target="_blank" rel="noopener">EXTRA_PREVIOUS_SCAN_MODE</a>，二者分别告知您新的和旧的扫描模式。 每个字段可能的值包括 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#SCAN_MODE_CONNECTABLE_DISCOVERABLE" target="_blank" rel="noopener">SCAN_MODE_CONNECTABLE_DISCOVERABLE</a>、 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#SCAN_MODE_CONNECTABLE" target="_blank" rel="noopener">SCAN_MODE_CONNECTABLE</a> 或 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#SCAN_MODE_NONE" target="_blank" rel="noopener">SCAN_MODE_NONE</a>，这些值分别指示设备处于可检测到模式、未处于可检测到模式但仍能接收连接，或未处于可检测到模式并且无法接收连接。</p>
<p>如果您将要发起到远程设备的连接，则无需启用设备可检测性。 仅当您希望您的应用托管将用于接受传入连接的服务器套接字时，才有必要启用可检测性，因为远程设备必须能够发现该设备，然后才能发起连接。</p>
<h1 id="连接设备"><a href="#连接设备" class="headerlink" title="连接设备"></a>连接设备</h1><p>要在两台设备上的应用之间创建连接，必须同时实现服务器端和客户端机制，因为其中一台设备必须开放服务器套接字，而另一台设备必须发起连接（使用服务器设备的 MAC 地址发起连接）。 当服务器和客户端在同一 RFCOMM 通道上分别拥有已连接的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 时，二者将被视为彼此连接。 这种情况下，每台设备都能获得输入和输出流式传输，并且可以开始传输数据，在有关<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#ManagingAConnection" target="_blank" rel="noopener">管理连接</a>的部分将会讨论这一主题。 本部分介绍如何在两台设备之间发起连接。</p>
<p>服务器设备和客户端设备分别以不同的方法获得需要的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。服务器将在传入连接被接受时收到套接字。 客户端将在其打开到服务器的 RFCOMM 通道时收到该套接字。</p>
<p><img src="/media/15138420406140.png" alt=""></p>
<p>图 3：蓝牙配对对话框。</p>
<p>一种实现技术是自动将每台设备准备为一个服务器，从而使每台设备开放一个服务器套接字并侦听连接。然后任一设备可以发起与另一台设备的连接，并成为客户端。 或者，其中一台设备可显式“托管”连接并按需开放一个服务器套接字，而另一台设备则直接发起连接。</p>
<blockquote>
<p><strong>注</strong>：如果两台设备之前尚未配对，则在连接过程中，Android 框架会自动向用户显示配对请求通知或对话框（如图 3 所示）。因此，在尝试连接设备时，您的应用无需担心设备是否已配对。 您的 RFCOMM 连接尝试将被阻塞，直至用户成功完成配对或配对失败（包括用户拒绝配对、配对失败或超时）。</p>
</blockquote>
<h1 id="连接为服务器"><a href="#连接为服务器" class="headerlink" title="连接为服务器"></a>连接为服务器</h1><p>当您需要连接两台设备时，其中一台设备必须通过保持开放的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 来充当服务器。 服务器套接字的用途是侦听传入的连接请求，并在接受一个请求后提供已连接的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。 从 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 后，可以（并且应该）舍弃 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a>，除非您需要接受更多连接。<br>以下是设置服务器套接字并接受连接的基本过程：</p>
<ol>
<li><p>通过调用</p>
<p> <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#listenUsingRfcommWithServiceRecord(java.lang.String, java.util.UUID" target="_blank" rel="noopener">listenUsingRfcommWithServiceRecord(String, UUID) </a>)获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a>。<br> 该字符串是您的服务的可识别名称，系统会自动将其写入到设备上的新服务发现协议 (SDP) 数据库条目（可使用任意名称，也可直接使用您的应用名称）。 UUID 也包含在 SDP 条目中，并且将作为与客户端设备的连接协议的基础。 也就是说，当客户端尝试连接此设备时，它会携带能够唯一标识其想要连接的服务的 UUID。 两个 UUID 必须匹配，在下一步中，连接才会被接受</p>
</li>
<li><p>通过调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#accept(" target="_blank" rel="noopener">accept()</a>) 开始侦听连接请求。</p>
<p> 这是一个阻塞调用。它将在连接被接受或发生异常时返回。 仅当远程设备发送的连接请求中所包含的 UUID 与向此侦听服务器套接字注册的 UUID 相匹配时，连接才会被接受。 操作成功后，<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#accept(" target="_blank" rel="noopener">accept()</a>) 将会返回已连接的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。</p>
</li>
<li><p>除非您想要接受更多连接，否则请调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#close(" target="_blank" rel="noopener">close()</a>)。</p>
<p> 这将释放服务器套接字及其所有资源，但不会关闭 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#accept(" target="_blank" rel="noopener">accept()</a>) 所返回的已连接的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。 与 TCP/IP 不同，RFCOMM 一次只允许每个通道有一个已连接的客户端，因此大多数情况下，在接受已连接的套接字后立即在 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 上调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#close(" target="_blank" rel="noopener">close()</a>) 是行得通的。</p>
</li>
</ol>
<p><a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#accept(" target="_blank" rel="noopener">accept()</a>) 调用不应在主 Activity UI 线程中执行，因为它是阻塞调用，并会阻止与应用的任何其他交互。 在您的应用所管理的新线程中使用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 或 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 完成所有工作，这通常是一种行之有效的做法。 要终止 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#accept(" target="_blank" rel="noopener">accept()</a>) 等被阻塞的调用，请通过另一个线程在 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a>（或 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>）上调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#close(" target="_blank" rel="noopener">close()</a>)，被阻塞的调用将会立即返回。 请注意，<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 或 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 中的所有方法都是线程安全的方法。</p>
<h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>以下是一个用于接受传入连接的服务器组件的简化线程：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">AcceptThread</span> <span class="keyword">extends</span> <span class="title">Thread</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BluetoothServerSocket mmServerSocket;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">AcceptThread</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// Use a temporary object that is later assigned to mmServerSocket,</span></span><br><span class="line">        <span class="comment">// because mmServerSocket is final</span></span><br><span class="line">        BluetoothServerSocket tmp = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// MY_UUID is the app's UUID string, also used by the client code</span></span><br><span class="line">            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123; &#125;</span><br><span class="line">        mmServerSocket = tmp;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        BluetoothSocket socket = <span class="keyword">null</span>;</span><br><span class="line">        <span class="comment">// Keep listening until exception occurs or a socket is returned</span></span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                socket = mmServerSocket.accept();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// If a connection was accepted</span></span><br><span class="line">            <span class="keyword">if</span> (socket != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="comment">// Do work to manage the connection (in a separate thread)</span></span><br><span class="line">                manageConnectedSocket(socket);</span><br><span class="line">                mmServerSocket.close();</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** Will cancel the listening socket, and cause the thread to finish */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            mmServerSocket.close();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在此示例中，只需要一个传入连接，因此在接受连接并获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 之后，应用会立即将获取的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 发送到单独的线程，关闭 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 并中断循环。</p>
<p>请注意，当 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#accept(" target="_blank" rel="noopener">accept()</a>) 返回 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 时，表示套接字已连接好，因此您不应该像在客户端那样调用 connect()。</p>
<p>manageConnectedSocket() 是应用中的虚构方法，它将启动用于传输数据的线程，在有关管理连接的部分将会讨论这一主题。</p>
<p>在完成传入连接的侦听后，通常应立即关闭您的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a>。 在此示例中，获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 后立即调用 close()。 您也可能希望在您的线程中提供一个公共方法，以便在需要停止侦听服务器套接字时关闭私有 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。</p>
<h1 id="连接为客户端"><a href="#连接为客户端" class="headerlink" title="连接为客户端"></a>连接为客户端</h1><p>要发起与远程设备（保持开放的服务器套接字的设备）的连接，必须首先获取表示该远程设备的 BluetoothDevice 对象。（在前面有关查找设备的部分介绍了如何获取 BluetoothDevice）。 然后必须使用 BluetoothDevice 来获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 并发起连接。</p>
<p>以下是基本过程：</p>
<ol>
<li><p>使用 BluetoothDevice，通过调用 createRfcommSocketToServiceRecord(UUID) 获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。</p>
<p> 这将初始化将要连接到 BluetoothDevice 的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。 此处传递的 UUID 必须与服务器设备在使用 listenUsingRfcommWithServiceRecord(String, UUID) 开放其 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html" target="_blank" rel="noopener">BluetoothServerSocket</a> 时所用的 UUID 相匹配。 要使用相同的 UUID，只需将该 UUID 字符串以硬编码方式编入应用，然后通过服务器代码和客户端代码引用该字符串。</p>
</li>
<li><p>通过调用 connect() 发起连接。</p>
<p> 执行此调用时，系统将会在远程设备上执行 SDP 查找，以便匹配 UUID。 如果查找成功并且远程设备接受了该连接，它将共享 RFCOMM 通道以便在连接期间使用，并且 connect() 将会返回。 此方法为阻塞调用。 如果由于任何原因连接失败或 connect() 方法超时（大约 12 秒之后），它将会引发异常。</p>
</li>
</ol>
<p>由于 connect() 为阻塞调用，因此该连接过程应始终在主 Activity 线程以外的线程中执行。</p>
<blockquote>
<p><strong>注</strong>：在调用 connect() 时，应始终确保设备未在执行设备发现。 如果正在进行发现操作，则会大幅降低连接尝试的速度，并增加连接失败的可能性。</p>
</blockquote>
<h2 id="示例-1"><a href="#示例-1" class="headerlink" title="示例"></a>示例</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">ConnectThread</span> <span class="keyword">extends</span> <span class="title">Thread</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BluetoothSocket mmSocket;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BluetoothDevice mmDevice;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ConnectThread</span><span class="params">(BluetoothDevice device)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// Use a temporary object that is later assigned to mmSocket,</span></span><br><span class="line">        <span class="comment">// because mmSocket is final</span></span><br><span class="line">        BluetoothSocket tmp = <span class="keyword">null</span>;</span><br><span class="line">        mmDevice = device;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Get a BluetoothSocket to connect with the given BluetoothDevice</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// MY_UUID is the app's UUID string, also used by the server code</span></span><br><span class="line">            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123; &#125;</span><br><span class="line">        mmSocket = tmp;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// Cancel discovery because it will slow down the connection</span></span><br><span class="line">        mBluetoothAdapter.cancelDiscovery();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// Connect the device through the socket. This will block</span></span><br><span class="line">            <span class="comment">// until it succeeds or throws an exception</span></span><br><span class="line">            mmSocket.connect();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException connectException) &#123;</span><br><span class="line">            <span class="comment">// Unable to connect; close the socket and get out</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                mmSocket.close();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException closeException) &#123; &#125;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Do work to manage the connection (in a separate thread)</span></span><br><span class="line">        manageConnectedSocket(mmSocket);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** Will cancel an in-progress connection, and close the socket */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            mmSocket.close();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>请注意，在建立连接之前会调用 cancelDiscovery()。 在进行连接之前应始终执行此调用，而且调用时无需实际检查其是否正在运行（但如果您确实想要执行检查，请调用 isDiscovering()）。</p>
<p>manageConnectedSocket() 是应用中的虚构方法，它将启动用于传输数据的线程，在有关管理连接的部分将会讨论这一主题。</p>
<p>在完成 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 后，应始终调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothServerSocket.html#close(" target="_blank" rel="noopener">close()</a>) 以执行清理操作。这样做会立即关闭已连接的套接字并清理所有内部资源。</p>
<h1 id="管理连接"><a href="#管理连接" class="headerlink" title="管理连接"></a>管理连接</h1><p>在成功连接两台（或更多台）设备后，每台设备都会有一个已连接的 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>。 这一点非常有趣，因为这表示您可以在设备之间共享数据。 利用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a>，传输任意数据的一般过程非常简单：</p>
<ol>
<li><p>获取 <a href="https://developer.android.google.cn/reference/java/io/InputStream.html" target="_blank" rel="noopener">InputStream</a> 和 <a href="https://developer.android.google.cn/reference/java/io/OutputStream.html" target="_blank" rel="noopener">OutputStream</a>，二者分别通过套接字以及 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html#getInputStream(" target="_blank" rel="noopener">getInputStream()</a>) 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html#getOutputStream(" target="_blank" rel="noopener">getOutputStream()</a>) 来处理数据传输。</p>
</li>
<li><p>使用 <a href="https://developer.android.google.cn/reference/java/io/InputStream.html#read(byte[]" target="_blank" rel="noopener">read(byte[])</a>) 和 <a href="https://developer.android.google.cn/reference/java/io/OutputStream.html#write(byte[]" target="_blank" rel="noopener">write(byte[])</a>) 读取数据并写入到流式传输。<br>就这么简单。</p>
</li>
</ol>
<p>当然，还需要考虑实现细节。首要的是，应该为所有流式传输读取和写入操作使用专门的线程。 这一点很重要，因为 read(byte[]) 和 write(byte[]) 方法都是阻塞调用。read(byte[]) 将会阻塞，直至从流式传输中读取内容。write(byte[]) 通常不会阻塞，但如果远程设备没有足够快地调用 read(byte[])，并且中间缓冲区已满，则其可能会保持阻塞状态以实现流量控制。因此，线程中的主循环应专门用于读取 <a href="https://developer.android.google.cn/reference/java/io/InputStream.html" target="_blank" rel="noopener">InputStream</a>。 可使用线程中单独的公共方法来发起对 <a href="https://developer.android.google.cn/reference/java/io/OutputStream.html" target="_blank" rel="noopener">OutputStream</a> 的写入操作。</p>
<h2 id="示例-2"><a href="#示例-2" class="headerlink" title="示例"></a>示例</h2><p>以下是可能的显示内容示例：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">ConnectedThread</span> <span class="keyword">extends</span> <span class="title">Thread</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BluetoothSocket mmSocket;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> InputStream mmInStream;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> OutputStream mmOutStream;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">ConnectedThread</span><span class="params">(BluetoothSocket socket)</span> </span>&#123;</span><br><span class="line">        mmSocket = socket;</span><br><span class="line">        InputStream tmpIn = <span class="keyword">null</span>;</span><br><span class="line">        OutputStream tmpOut = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Get the input and output streams, using temp objects because</span></span><br><span class="line">        <span class="comment">// member streams are final</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            tmpIn = socket.getInputStream();</span><br><span class="line">            tmpOut = socket.getOutputStream();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123; &#125;</span><br><span class="line"></span><br><span class="line">        mmInStream = tmpIn;</span><br><span class="line">        mmOutStream = tmpOut;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">byte</span>[] buffer = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];  <span class="comment">// buffer store for the stream</span></span><br><span class="line">        <span class="keyword">int</span> bytes; <span class="comment">// bytes returned from read()</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// Keep listening to the InputStream until an exception occurs</span></span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// Read from the InputStream</span></span><br><span class="line">                bytes = mmInStream.read(buffer);</span><br><span class="line">                <span class="comment">// Send the obtained bytes to the UI activity</span></span><br><span class="line">                mHandler.obtainMessage(MESSAGE_READ, bytes, -<span class="number">1</span>, buffer)</span><br><span class="line">                        .sendToTarget();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Call this from the main activity to send data to the remote device */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span><span class="params">(<span class="keyword">byte</span>[] bytes)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            mmOutStream.write(bytes);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123; &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Call this from the main activity to shutdown the connection */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancel</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            mmSocket.close();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>构造函数获取必要的流式传输，并且一旦执行，线程将会等待通过 InputStream 传入的数据。 当 read(byte[]) 返回流式传输中的字节时，将使用来自父类的成员处理程序将数据发送到主 Activity。 然后该方法返回并等待流式传输提供更多字节。</p>
<p>发送传出数据不外乎从主 Activity 调用线程的 write() 方法，并传入要发送的字节。 然后，此方法直接调用 write(byte[])，将数据发送到远程设备。</p>
<p>线程的 cancel() 方法很重要，它能通过关闭 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothSocket.html" target="_blank" rel="noopener">BluetoothSocket</a> 随时终止连接。当您结束蓝牙连接的使用时，应始终调用此方法。</p>
<p>有关使用 Bluetooth API 的演示，请参阅蓝牙聊天示例应用。</p>
<h1 id="使用配置文件"><a href="#使用配置文件" class="headerlink" title="使用配置文件"></a>使用配置文件</h1><p>从 Android 3.0 开始，Bluetooth API 便支持使用蓝牙配置文件。 蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。 免提配置文件便是一个示例。 对于连接到无线耳机的手机，两台设备都必须支持免提配置文件。</p>
<p>您可以实现接口 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.html" target="_blank" rel="noopener">BluetoothProfile</a>，通过写入自己的类来支持特定的蓝牙配置文件。 Android Bluetooth API 提供了以下蓝牙配置文件的实现：</p>
<ul>
<li><p><strong>耳机</strong>。耳机配置文件提供了蓝牙耳机支持，以便与手机配合使用。 Android 提供了 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHeadset.html" target="_blank" rel="noopener">BluetoothHeadset</a> 类，它是用于通过进程间通信 (<a href="https://developer.android.google.cn/guide/components/processes-and-threads.html#IPC" target="_blank" rel="noopener">IPC</a>) 来控制蓝牙耳机服务的代理。 这包括蓝牙耳机和免提（1.5 版）配置文件。 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHeadset.html" target="_blank" rel="noopener">BluetoothHeadset</a> 类包含 AT 命令支持。 有关此主题的详细讨论，请参阅供应商特定的 AT 命令</p>
</li>
<li><p><strong>A2DP</strong>。高级音频分发配置文件 (A2DP) 定义了高质量音频如何通过蓝牙连接和流式传输，从一个设备传输到另一个设备。 Android 提供了 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothA2dp.html" target="_blank" rel="noopener">BluetoothA2dp</a> 类，它是用于通过 IPC 来控制蓝牙 A2DP 服务的代理。</p>
</li>
<li><p><strong>健康设备</strong>。Android 4.0（API 级别 14）引入了对蓝牙健康设备配置文件 (HDP) 的支持。 这允许您创建应用，使用蓝牙与支持蓝牙功能的健康设备进行通信，例如心率监测仪、血糖仪、温度计、台秤等等。 有关支持的设备列表及其相应的设备数据专业化代码，请参阅 <a href="http://www.bluetooth.org" target="_blank" rel="noopener">www.bluetooth.org</a> 上的蓝牙分配编号。 请注意，这些值在 ISO/IEEE 11073-20601 [7] 规范的“命名法规附录”中也被称为 MDC_DEV_SPEC_PROFILE_*。 有关 HDP 的详细讨论，请参阅<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#HDP" target="_blank" rel="noopener">健康设备配置文件</a>。</p>
</li>
</ul>
<p>以下是使用配置文件的基本步骤：</p>
<ol>
<li>获取默认适配器（请参阅<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#SettingUp" target="_blank" rel="noopener">设置蓝牙</a>）。</li>
<li>使用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int" target="_blank" rel="noopener">getProfileProxy()</a>) 建立到配置文件所关联的配置文件代理对象的连接。在以下示例中，配置文件代理对象是一个 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHeadset.html" target="_blank" rel="noopener">BluetoothHeadset</a> 的实例。</li>
<li>设置 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.ServiceListener.html" target="_blank" rel="noopener">BluetoothProfile.ServiceListener</a>。此侦听程序会在 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.html" target="_blank" rel="noopener">BluetoothProfile</a> IPC 客户端连接到服务或断开服务连接时向其发送通知。</li>
<li>在 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.ServiceListener.html#onServiceConnected(int, android.bluetooth.BluetoothProfile" target="_blank" rel="noopener">onServiceConnected()</a>) 中，获取配置文件代理对象的句柄。</li>
<li>获得配置文件代理对象后，可以立即将其用于监视连接状态和执行其他与该配置文件相关的操作。</li>
</ol>
<p>例如，以下代码片段显示了如何连接到 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHeadset.html" target="_blank" rel="noopener">BluetoothHeadset</a> 代理对象，以便能够控制耳机配置文件：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">BluetoothHeadset mBluetoothHeadset;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get the default adapter</span></span><br><span class="line">BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Establish connection to the proxy.</span></span><br><span class="line">mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> BluetoothProfile.ServiceListener mProfileListener = <span class="keyword">new</span> BluetoothProfile.ServiceListener() &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServiceConnected</span><span class="params">(<span class="keyword">int</span> profile, BluetoothProfile proxy)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (profile == BluetoothProfile.HEADSET) &#123;</span><br><span class="line">            mBluetoothHeadset = (BluetoothHeadset) proxy;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServiceDisconnected</span><span class="params">(<span class="keyword">int</span> profile)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (profile == BluetoothProfile.HEADSET) &#123;</span><br><span class="line">            mBluetoothHeadset = <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ... call functions on mBluetoothHeadset</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Close proxy connection after use.</span></span><br><span class="line">mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);</span><br></pre></td></tr></table></figure>
<h1 id="供应商特定的-AT-命令"><a href="#供应商特定的-AT-命令" class="headerlink" title="供应商特定的 AT 命令"></a>供应商特定的 AT 命令</h1><p>从 Android 3.0 开始，应用可以注册接收耳机所发送的预定义的供应商特定 AT 命令的系统广播（例如 Plantronics +XEVENT 命令）。 例如，应用可以接收指示所连接设备的电池电量的广播，并根据需要通知用户或采取其他操作。 为 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHeadset.html#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT" target="_blank" rel="noopener">ACTION_VENDOR_SPECIFIC_HEADSET_EVENT</a> intent 创建广播接收器，以处理耳机的供应商特定 AT 命令。</p>
<h1 id="健康设备配置文件"><a href="#健康设备配置文件" class="headerlink" title="健康设备配置文件"></a>健康设备配置文件</h1><p>Android 4.0（API 级别 14）引入了对蓝牙健康设备配置文件 (HDP) 的支持。 这允许您创建应用，使用蓝牙与支持蓝牙功能的健康设备进行通信，例如心率监测仪、血糖仪、温度计、台秤等等。 Bluetooth Health API 包括类 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealth.html" target="_blank" rel="noopener">BluetoothHealth</a>、<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealthCallback.html" target="_blank" rel="noopener">BluetoothHealthCallback</a> 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealthAppConfiguration.html" target="_blank" rel="noopener">BluetoothHealthAppConfiguration</a>，在<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#TheBasicsw" target="_blank" rel="noopener">基础知识</a>部分介绍了这些类。</p>
<p>在使用 Bluetooth Health API 时，了解以下关键 HDP 概念很有帮助：</p>
<table>
<thead>
<tr>
<th>概念</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>源设备<span class="Apple-tab-span" style="white-space:pre"></span></td>
<td>在 HDP 中定义的角色。源设备是将医疗数据传输到 Android 手机或平板电脑等智能设备的健康设备（体重秤、血糖仪、温度计等）。</td>
</tr>
<tr>
<td>汇集设备</td>
<td>在 HDP 中定义的角色。在 HDP 中，汇集设备是接收医疗数据的智能设备。 在 Android HDP 应用中，汇集设备表示为 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealthAppConfiguration.html" target="_blank" rel="noopener">BluetoothHealthAppConfiguration</a> 对象。</td>
</tr>
<tr>
<td>注册</td>
<td>指的是注册特定健康设备的汇集设备。</td>
</tr>
<tr>
<td>连接</td>
<td>指的是开放健康设备与 Android 手机或平板电脑等智能设备之间的通道。</td>
</tr>
</tbody>
</table>
<h1 id="创建-HDP-应用"><a href="#创建-HDP-应用" class="headerlink" title="创建 HDP 应用"></a>创建 HDP 应用</h1><p>以下是创建 Android HDP 应用所涉及的基本步骤：</p>
<ol>
<li><p>获取 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealth.html" target="_blank" rel="noopener">BluetoothHealth</a> 代理对象的引用。</p>
<p> 与常规耳机和 A2DP 配置文件设备相似，您必须使用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.ServiceListener.html" target="_blank" rel="noopener">BluetoothProfile.ServiceListener</a> 和 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothProfile.html#HEALTH" target="_blank" rel="noopener">HEALTH</a> 配置文件类型来调用 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter.html#getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int" target="_blank" rel="noopener">getProfileProxy()</a>)，以便与配置文件代理对象建立连接。</p>
</li>
<li><p>创建 <a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealthCallback.html" target="_blank" rel="noopener">BluetoothHealthCallback</a> 并注册充当健康汇集设备的应用配置 (<a href="https://developer.android.google.cn/reference/android/bluetooth/BluetoothHealthAppConfiguration.html" target="_blank" rel="noopener">BluetoothHealthAppConfiguration</a>)。</p>
</li>
<li><p>建立到健康设备的连接。一些设备将会发起该连接。 对于这类设备，无需执行该步骤。</p>
</li>
<li><p>成功连接到健康设备后，使用文件描述符对健康设备执行读/写操作。接收的数据需要使用实现了 IEEE 11073-xxxxx 规范的健康管理器进行解释。</p>
</li>
<li><p>完成后，关闭健康通道并取消注册该应用。该通道在长期闲置时也会关闭。</p>
</li>
</ol>
<p>有关描述上述步骤的完整代码示例，请参阅<a href="https://developer.android.google.cn/resources/samples/BluetoothHDP/index.html" target="_blank" rel="noopener">蓝牙 HDP（健康设备配置文件）</a>。</p>
<p>原文地址:<a href="https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#ManagingAConnection" target="_blank" rel="noopener">https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html#ManagingAConnection</a></p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color5">bluetooth</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/639eaf27.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-Android低功耗蓝牙" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/647d650a.html">Android低功耗蓝牙</a>
    </h1>
  

        
        <a href="/post/647d650a.html" class="archive-article-date">
  	<time datetime="2017-12-21T06:58:17.709Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2017-12-21</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h1 id="关键术语和概念"><a href="#关键术语和概念" class="headerlink" title="关键术语和概念"></a>关键术语和概念</h1><h2 id="下面是一些BLE关键术语和概念的摘要"><a href="#下面是一些BLE关键术语和概念的摘要" class="headerlink" title="下面是一些BLE关键术语和概念的摘要"></a>下面是一些BLE关键术语和概念的摘要</h2><ul>
<li>Generic Attribute Profile（GATT）：GATT profile是一种关于发送和接收简短数据片段的一般规范，这种简短数据片段例如在BLE的连接上众所周知的“attribute（属性）”等。当前所有低功耗应用程序的profile都基于GATT。另外，蓝牙技术联盟（Bluetooth SIG）已经为很多BLE设备定义了profile。profile就是一种在指定的应用程序中定义设备如何工作的规范。注意，一台设备可以实现多个profile。例如，一台设备可以包含心率监视器和电池电量探测器。</li>
<li>Attribute Protocol（ATT，属性协议）：GATT构建在ATT的基础之上，因此也总被成为GATT/ATT。ATT针对BLE设备的运行进行了优化。为此，它会尽可能的使用更少的字节数据。每一个属性都通过UUID来唯一确定。UUID就是一个标准128位格式的字符串ID，用于唯一确定一个信息。属性通过ATT协议格式化为characteristics和services后进行传输。</li>
<li>Characteristic：一个characteristic中包含一个值，以及0个或多个用于描述characteristic值的descriptor。可以将characteristic认为是一种类型，类似于一个类。</li>
<li>Descriptor：Descriptor（描述符）中定义的属性用于描述一个characteristic值。例如，一个descriptor可以为一个characteristic的值指定一个在可接受范围内的可读性描述，或者为一个characteristic的值指定一个计量单位。</li>
<li>Service：一个service是一个characteristic的集合。例如，你可以持有一个名为“心率监视器”的service，它包含的characteristic例如“心率测量”。你可以在bluetooth.org上找到一系列基于GATT的profile和service。</li>
</ul>
<h2 id="角色和职能"><a href="#角色和职能" class="headerlink" title="角色和职能"></a>角色和职能</h2><p>以下是一台Android设备与BLE设备交互时的一些适用角色和职能：</p>
<ul>
<li><p>中央设备和外围设备。这适用于BLE自身的连接。担任中央设备角色的设备负责扫描和搜索广告，担任外围设备的角色负责发送广告。</p>
</li>
<li><p>GATT服务端和GATT客户端。这取决于两台设备在建立连接后如何互相通信。</p>
</li>
</ul>
<p>为了理解这之间的区别，想象你有一台Android手机和一台BLE设备作为活动追踪器。手机将担任中央设备角色；活动追踪器将担任外围设备角色（你需要具备两种角色才能建立一个BLE连接，两者都担任外围设备角色不能互相通信，同样两者都担任中央设备角色也不能互相通信）。</p>
<p>一旦手机和活动追踪器建立连接，它们就可以互相传输GATT媒体数据。根据它们传输的数据，其中一方需要担任服务端的角色。例如，如果活动追踪器想要发送传感器数据给手机，活动追踪器就需要担任服务端的角色。如果活动追踪器想要接收手机的数据，手机就需要担任服务端的角色。</p>
<p>在本片文档的例子中，Android应用程序（运行在Android设备上）是GATT客户端。应用程序从GATT服务端获取数据，这个服务端由支持Heart Rate Profile的BLE心率监视器设备担任。但是你可以交替让你的Android应用程序扮演GATT服务端的角色。具体参考<a href="http://developer.android.com/reference/android/bluetooth/BluetoothGattServer.html" target="_blank" rel="noopener">BluetoothGattService</a>。</p>
<h1 id="BLE-Permissions（BLE权限）"><a href="#BLE-Permissions（BLE权限）" class="headerlink" title="BLE Permissions（BLE权限）"></a>BLE Permissions（BLE权限）</h1><p>为了在你的应用程序中使用Bluetooth的功能，你必须声明android.permission.BLUETOOTH权限。你需要这个权限来执行一些蓝牙通信的操作，例如请求链接，接受连接，还有传输数据。</p>
<p>如果你想让你的应用程序进行设备扫描或者管理蓝牙设置，你必须同时声明android.permission.BLUETOOTH_ADMIN权限。注意，如果你使用BLUETOOTH_ADMIN权限，你必须同时声明BLUETOOTH权限。</p>
<p>在你的应用程序manifest文件中声明蓝牙权限，例如：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.BLUETOOTH"</span>/&gt;</span>  </span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.BLUETOOTH_ADMIN"</span>/&gt;</span></span><br></pre></td></tr></table></figure>
<p>如果你想声明你的应用程序只能在支持BLE的设备上运行，可以将下面声明包含进你的应用程序manifest文件中：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">uses-feature</span> <span class="attr">android:name</span>=<span class="string">"android.hardware.bluetooth_le"</span> <span class="attr">android:required</span>=<span class="string">"true"</span>/&gt;</span></span><br></pre></td></tr></table></figure>
<p>然而，如果你想让你的应用程序也能够在不支持BLE的设备上运行，你就应该将上面标签中的属性设置为<code>required=&quot;false&quot;</code>。然后在运行的过程中使用<code>PackageManager.hasSystemFeature()</code>方法来判断设备是否支持BLE：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用下面的方法来确定设备是否支持BLE, 然后你可以选择禁用BLE的功能  </span></span><br><span class="line"><span class="keyword">if</span> (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) &#123;  </span><br><span class="line">    Toast.makeText(<span class="keyword">this</span>, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();  </span><br><span class="line">    finish();  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="Setting-Up-BLE（设置BLE）"><a href="#Setting-Up-BLE（设置BLE）" class="headerlink" title="Setting Up BLE（设置BLE）"></a>Setting Up BLE（设置BLE）</h1><p>在你的应用程序通过BLE进行通信之前，你需要确认设备是否支持BLE。如果支持，还要再确认是否已经启用。注意这个检查步骤只有在<code>&lt;uses-festure.../&gt;</code>设置为<code>false</code>的情况下才需要执行。</p>
<p>如果不支持BLE，你应该优雅的禁止一些使用BLE的功能。如果支持BLE，但是目前禁用了，那么你需要在不离开你的应用程序状态下，请求用户启用蓝牙用能。这个过程需要使用BluetoothAdapter，分两个步骤完成：</p>
<h2 id="获取BluetoothAdapter"><a href="#获取BluetoothAdapter" class="headerlink" title="获取BluetoothAdapter"></a>获取BluetoothAdapter</h2><p>基本上所有使用蓝牙的activity都需要BluetoothAdapter。BluetoothAdapter代表了设备本身的蓝牙适配器（蓝牙发送接收器）。在整个系统中有一个BluetoothAdapter对象，你的应用程序可以使用这个对象进行交互。下面的代码片段展示了如果获取这个适配器。注意下面的这种方法使用getSystemService()方法来获取一个BluetoothManager实例，之后再通过BluetoothManager获取BluetoothAdapter。Android 4.3（API Level 18）才开始支持BluetoothManager：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始化蓝牙适配器.  </span></span><br><span class="line"><span class="keyword">final</span> BluetoothManager bluetoothManager =  </span><br><span class="line">        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);  </span><br><span class="line">mBluetoothAdapter = bluetoothManager.getAdapter();</span><br></pre></td></tr></table></figure>
<h2 id="启用蓝牙"><a href="#启用蓝牙" class="headerlink" title="启用蓝牙"></a>启用蓝牙</h2><p>下一步，你需要确保蓝牙已经启动。调用<code>isEnable()</code>方法来检查蓝牙当前是否已经启用。如果方法返回false，说明蓝牙被禁用了。下面的代码片段中检查蓝牙是否已经启用。如果没有启用，代码片段会显示一个错误提示用户去设置中启用蓝牙：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> BluetoothAdapter mBluetoothAdapter;  </span><br><span class="line">...  </span><br><span class="line"><span class="comment">// 确认设备支持蓝牙并且已经启用. 如果没有,  </span></span><br><span class="line"><span class="comment">// 显示一个对话框要求用户授权启用蓝牙.  </span></span><br><span class="line"><span class="keyword">if</span> (mBluetoothAdapter == <span class="keyword">null</span> || !mBluetoothAdapter.isEnabled()) &#123;  </span><br><span class="line">    Intent enableBtIntent = <span class="keyword">new</span> Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  </span><br><span class="line">    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="Finding-BLE-Devices（搜索BLE设备）"><a href="#Finding-BLE-Devices（搜索BLE设备）" class="headerlink" title="Finding BLE Devices（搜索BLE设备）"></a>Finding BLE Devices（搜索BLE设备）</h1><p>搜索BLE设备，你可以使用startLeScan()方法。这个方法需要一个BluetoothAdapter.LeScanCallback对象作为参数。你必须实现这个callback接口，因为扫描的结果会通过这个接口返回。由于搜索设备是比较耗电的操作，你应该遵循以下指南使用：</p>
<ul>
<li><p>一旦你找到目标设备，应该马上停止搜索。</p>
</li>
<li><p>不要死循环搜索，并设置搜索的最长时间。一台以前可以访问的设备可能已经移出了可检测范围，继续扫描只会消耗电量。</p>
</li>
</ul>
<p>下面的代码片段展示了如何开始和停止搜索</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 扫描和显示可访问BLE设备的Activity.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeviceScanActivity</span> <span class="keyword">extends</span> <span class="title">ListActivity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> BluetoothAdapter mBluetoothAdapter;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">boolean</span> mScanning;</span><br><span class="line">    <span class="keyword">private</span> Handler mHandler;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 10秒钟后停止扫描.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> SCAN_PERIOD = <span class="number">10000</span>;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">scanLeDevice</span><span class="params">(<span class="keyword">final</span> <span class="keyword">boolean</span> enable)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (enable) &#123;</span><br><span class="line">            <span class="comment">// 在预定义的扫描时间周期后停止扫描.</span></span><br><span class="line">            mHandler.postDelayed(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                    mScanning = <span class="keyword">false</span>;</span><br><span class="line">                    mBluetoothAdapter.stopLeScan(mLeScanCallback);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;, SCAN_PERIOD);</span><br><span class="line"></span><br><span class="line">            mScanning = <span class="keyword">true</span>;</span><br><span class="line">            mBluetoothAdapter.startLeScan(mLeScanCallback);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            mScanning = <span class="keyword">false</span>;</span><br><span class="line">            mBluetoothAdapter.stopLeScan(mLeScanCallback);</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果你只想搜索指定类型的外围设备，你可以替换成startLeScan(UUID[], BluetoothAdapter.LeScanCallback)方法，并提供一个你的应用程序所支持的GATT服务的UUID对象数组。</p>
<p>下面是一个BluetoothAdapter.LeScanCallback的实现，它是一个用于接收BLE搜索结果的接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> LeDeviceListAdapter mLeDeviceListAdapter;</span><br><span class="line">...</span><br><span class="line"><span class="comment">// 设备搜索回调接口.</span></span><br><span class="line"><span class="keyword">private</span> BluetoothAdapter.LeScanCallback mLeScanCallback =</span><br><span class="line">        <span class="keyword">new</span> BluetoothAdapter.LeScanCallback() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onLeScan</span><span class="params">(<span class="keyword">final</span> BluetoothDevice device, <span class="keyword">int</span> rssi,</span></span></span><br><span class="line"><span class="function"><span class="params">            <span class="keyword">byte</span>[] scanRecord)</span> </span>&#123;</span><br><span class="line">        runOnUiThread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">           <span class="meta">@Override</span></span><br><span class="line">           <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">               mLeDeviceListAdapter.addDevice(device);</span><br><span class="line">               mLeDeviceListAdapter.notifyDataSetChanged();</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;);</span><br><span class="line">   &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>注意：正如Bluetooth文档中所描述的，在同一个时间你只能搜索BLE设备或者搜索传统蓝牙设备。你不能同时搜索BLE设备和传统蓝牙设备。</p>
<h1 id="Connecting-to-a-GATT-Server（连接一个GATT服务）"><a href="#Connecting-to-a-GATT-Server（连接一个GATT服务）" class="headerlink" title="Connecting to a GATT Server（连接一个GATT服务）"></a>Connecting to a GATT Server（连接一个GATT服务）</h1><p>与BLE设备交互的第一步就是要连接上它——更准确的说，是连接设备上的GATT服务。连接BLE设备上的GATT服务，你可以使用connectGatt()方法。这个方法需要三个参数：一个Context对象，autoConnect（一个表示是否当BLE设备可访问时马上自动连接的boolean值），还有一个BluetoothGattCallback对象:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mBluetoothGatt = device.connectGatt(<span class="keyword">this</span>, <span class="keyword">false</span>, mGattCallback);</span><br></pre></td></tr></table></figure>
<p>上面的代码会连接BLE设备管理的GATT服务，并返回一个BluetoothGatt实例，通过这个实例就可以执行GATT客户端的相关操作。这个调用者（Android应用程序）就是GATT客户端。里面的BluetoothGattCallback对象用于交付操作结果给客户端，例如连接状态，还有将来一些GATT客户端操作的结果。</p>
<p>在这个例子中，BLE应用程序提供了一个activity（DeviceControlActivity）来连接、显示数据，和显示BLE设备所支持的GATT的service以及characteristic。基于用户的输入，这个activity会和一个名为BluetoothLeService的Service通信，这个service通过Android BLE的API与BLE设备进行交互。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一个通过Android BLE API与BLE设备进行交互的service.</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BluetoothLeService</span> <span class="keyword">extends</span> <span class="title">Service</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> String TAG = BluetoothLeService.class.getSimpleName();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> BluetoothManager mBluetoothManager;</span><br><span class="line">    <span class="keyword">private</span> BluetoothAdapter mBluetoothAdapter;</span><br><span class="line">    <span class="keyword">private</span> String mBluetoothDeviceAddress;</span><br><span class="line">    <span class="keyword">private</span> BluetoothGatt mBluetoothGatt;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> mConnectionState = STATE_DISCONNECTED;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> STATE_DISCONNECTED = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> STATE_CONNECTING = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> STATE_CONNECTED = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String ACTION_GATT_CONNECTED =</span><br><span class="line">            <span class="string">"com.example.bluetooth.le.ACTION_GATT_CONNECTED"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String ACTION_GATT_DISCONNECTED =</span><br><span class="line">            <span class="string">"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String ACTION_GATT_SERVICES_DISCOVERED =</span><br><span class="line">            <span class="string">"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String ACTION_DATA_AVAILABLE =</span><br><span class="line">            <span class="string">"com.example.bluetooth.le.ACTION_DATA_AVAILABLE"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> String EXTRA_DATA =</span><br><span class="line">            <span class="string">"com.example.bluetooth.le.EXTRA_DATA"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">static</span> UUID UUID_HEART_RATE_MEASUREMENT =</span><br><span class="line">            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// BLE API定义的各个回调方法.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BluetoothGattCallback mGattCallback =</span><br><span class="line">            <span class="keyword">new</span> BluetoothGattCallback() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onConnectionStateChange</span><span class="params">(BluetoothGatt gatt, <span class="keyword">int</span> status,</span></span></span><br><span class="line"><span class="function"><span class="params">                <span class="keyword">int</span> newState)</span> </span>&#123;</span><br><span class="line">            String intentAction;</span><br><span class="line">            <span class="keyword">if</span> (newState == BluetoothProfile.STATE_CONNECTED) &#123;</span><br><span class="line">                intentAction = ACTION_GATT_CONNECTED;</span><br><span class="line">                mConnectionState = STATE_CONNECTED;</span><br><span class="line">                broadcastUpdate(intentAction);</span><br><span class="line">                Log.i(TAG, <span class="string">"连接GATT服务."</span>);</span><br><span class="line">                Log.i(TAG, <span class="string">"尝试开始service搜索:"</span> +</span><br><span class="line">                        mBluetoothGatt.discoverServices());</span><br><span class="line"></span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (newState == BluetoothProfile.STATE_DISCONNECTED) &#123;</span><br><span class="line">                intentAction = ACTION_GATT_DISCONNECTED;</span><br><span class="line">                mConnectionState = STATE_DISCONNECTED;</span><br><span class="line">                Log.i(TAG, <span class="string">"断开GATT server连接."</span>);</span><br><span class="line">                broadcastUpdate(intentAction);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="comment">// New services discovered</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onServicesDiscovered</span><span class="params">(BluetoothGatt gatt, <span class="keyword">int</span> status)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (status == BluetoothGatt.GATT_SUCCESS) &#123;</span><br><span class="line">                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                Log.w(TAG, <span class="string">"onServicesDiscovered received: "</span> + status);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="comment">// Result of a characteristic read operation</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCharacteristicRead</span><span class="params">(BluetoothGatt gatt,</span></span></span><br><span class="line"><span class="function"><span class="params">                BluetoothGattCharacteristic characteristic,</span></span></span><br><span class="line"><span class="function"><span class="params">                <span class="keyword">int</span> status)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (status == BluetoothGatt.GATT_SUCCESS) &#123;</span><br><span class="line">                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">     ...</span><br><span class="line">    &#125;;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>当一个特定的的回调方法被调用的时候，它就会适当调用broadcastUpdate()帮助方法并传递一个操作标识。注意本节中的数据是根据蓝牙心率测量的profile规范解析的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">broadcastUpdate</span><span class="params">(<span class="keyword">final</span> String action)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Intent intent = <span class="keyword">new</span> Intent(action);</span><br><span class="line">    sendBroadcast(intent);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">broadcastUpdate</span><span class="params">(<span class="keyword">final</span> String action,</span></span></span><br><span class="line"><span class="function"><span class="params">                             <span class="keyword">final</span> BluetoothGattCharacteristic characteristic)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Intent intent = <span class="keyword">new</span> Intent(action);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 按照心率测量的profile进行的特定处理.</span></span><br><span class="line">    <span class="comment">// 按照么一个profile规范进行数据解析.</span></span><br><span class="line">    <span class="keyword">if</span> (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) &#123;</span><br><span class="line">        <span class="keyword">int</span> flag = characteristic.getProperties();</span><br><span class="line">        <span class="keyword">int</span> format = -<span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> ((flag &amp; <span class="number">0x01</span>) != <span class="number">0</span>) &#123;</span><br><span class="line">            format = BluetoothGattCharacteristic.FORMAT_UINT16;</span><br><span class="line">            Log.d(TAG, <span class="string">"Heart rate format UINT16."</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            format = BluetoothGattCharacteristic.FORMAT_UINT8;</span><br><span class="line">            Log.d(TAG, <span class="string">"Heart rate format UINT8."</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">int</span> heartRate = characteristic.getIntValue(format, <span class="number">1</span>);</span><br><span class="line">        Log.d(TAG, String.format(<span class="string">"Received heart rate: %d"</span>, heartRate));</span><br><span class="line">        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 针对其他profiles, 将数据格式化为16禁止数据.</span></span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">byte</span>[] data = characteristic.getValue();</span><br><span class="line">        <span class="keyword">if</span> (data != <span class="keyword">null</span> &amp;&amp; data.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">final</span> StringBuilder stringBuilder = <span class="keyword">new</span> StringBuilder(data.length);</span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">byte</span> byteChar : data)</span><br><span class="line">                stringBuilder.append(String.format(<span class="string">"%02X "</span>, byteChar));</span><br><span class="line">            intent.putExtra(EXTRA_DATA, <span class="keyword">new</span> String(data) + <span class="string">"\n"</span> +</span><br><span class="line">                    stringBuilder.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    sendBroadcast(intent);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>回到DeviceControlActivity，下面的事件通过一个BroadcaseReceiver处理：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 处理Service发送过来的各种时间.</span></span><br><span class="line"><span class="comment">// ACTION_GATT_CONNECTED: 连接上了一个GATT服务.</span></span><br><span class="line"><span class="comment">// ACTION_GATT_DISCONNECTED: 断开了一个GATT服务.</span></span><br><span class="line"><span class="comment">// ACTION_GATT_SERVICES_DISCOVERED: 发现了GATT服务.</span></span><br><span class="line"><span class="comment">// ACTION_DATA_AVAILABLE: 从设备接收到数据. 这里可能是一个读取或者通知操作的结果。</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> BroadcastReceiver mGattUpdateReceiver = <span class="keyword">new</span> BroadcastReceiver() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceive</span><span class="params">(Context context, Intent intent)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">final</span> String action = intent.getAction();</span><br><span class="line">        <span class="keyword">if</span> (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) &#123;</span><br><span class="line">            mConnected = <span class="keyword">true</span>;</span><br><span class="line">            updateConnectionState(R.string.connected);</span><br><span class="line">            invalidateOptionsMenu();</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) &#123;</span><br><span class="line">            mConnected = <span class="keyword">false</span>;</span><br><span class="line">            updateConnectionState(R.string.disconnected);</span><br><span class="line">            invalidateOptionsMenu();</span><br><span class="line">            clearUI();</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (BluetoothLeService.</span><br><span class="line">                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) &#123;</span><br><span class="line">            <span class="comment">// 显示所有支持的service和characteristic。</span></span><br><span class="line">            displayGattServices(mBluetoothLeService.getSupportedGattServices());</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) &#123;</span><br><span class="line">            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h1 id="Reading-BLE-Attribute（读取BLE属性）"><a href="#Reading-BLE-Attribute（读取BLE属性）" class="headerlink" title="Reading BLE Attribute（读取BLE属性）"></a>Reading BLE Attribute（读取BLE属性）</h1><p>一旦你的Android应用程序连接上了一个GATT服务并且发现了设备上的service，就可以在支持读写的地方读写属性。例如，下面的代码片段通过迭代服务的service和characteristic，并将它们显示在界面上：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeviceControlActivity</span> <span class="keyword">extends</span> <span class="title">Activity</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 演示如何迭代所支持的GATT Services/Characteristics.</span></span><br><span class="line">    <span class="comment">// 在这个例子中，我们填充绑定到ExpandableListView的数据结构。</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">displayGattServices</span><span class="params">(List&lt;BluetoothGattService&gt; gattServices)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (gattServices == <span class="keyword">null</span>) <span class="keyword">return</span>;</span><br><span class="line">        String uuid = <span class="keyword">null</span>;</span><br><span class="line">        String unknownServiceString = getResources().</span><br><span class="line">                getString(R.string.unknown_service);</span><br><span class="line">        String unknownCharaString = getResources().</span><br><span class="line">                getString(R.string.unknown_characteristic);</span><br><span class="line">        ArrayList&lt;HashMap&lt;String, String&gt;&gt; gattServiceData =</span><br><span class="line">                <span class="keyword">new</span> ArrayList&lt;HashMap&lt;String, String&gt;&gt;();</span><br><span class="line">        ArrayList&lt;ArrayList&lt;HashMap&lt;String, String&gt;&gt;&gt; gattCharacteristicData</span><br><span class="line">                = <span class="keyword">new</span> ArrayList&lt;ArrayList&lt;HashMap&lt;String, String&gt;&gt;&gt;();</span><br><span class="line">        mGattCharacteristics =</span><br><span class="line">                <span class="keyword">new</span> ArrayList&lt;ArrayList&lt;BluetoothGattCharacteristic&gt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 循环迭代可访问的GATT Services.</span></span><br><span class="line">        <span class="keyword">for</span> (BluetoothGattService gattService : gattServices) &#123;</span><br><span class="line">            HashMap&lt;String, String&gt; currentServiceData =</span><br><span class="line">                    <span class="keyword">new</span> HashMap&lt;String, String&gt;();</span><br><span class="line">            uuid = gattService.getUuid().toString();</span><br><span class="line">            currentServiceData.put(</span><br><span class="line">                    LIST_NAME, SampleGattAttributes.</span><br><span class="line">                            lookup(uuid, unknownServiceString));</span><br><span class="line">            currentServiceData.put(LIST_UUID, uuid);</span><br><span class="line">            gattServiceData.add(currentServiceData);</span><br><span class="line"></span><br><span class="line">            ArrayList&lt;HashMap&lt;String, String&gt;&gt; gattCharacteristicGroupData =</span><br><span class="line">                    <span class="keyword">new</span> ArrayList&lt;HashMap&lt;String, String&gt;&gt;();</span><br><span class="line">            List&lt;BluetoothGattCharacteristic&gt; gattCharacteristics =</span><br><span class="line">                    gattService.getCharacteristics();</span><br><span class="line">            ArrayList&lt;BluetoothGattCharacteristic&gt; charas =</span><br><span class="line">                    <span class="keyword">new</span> ArrayList&lt;BluetoothGattCharacteristic&gt;();</span><br><span class="line">           <span class="comment">// 循环迭代可访问的Characteristics.</span></span><br><span class="line">            <span class="keyword">for</span> (BluetoothGattCharacteristic gattCharacteristic :</span><br><span class="line">                    gattCharacteristics) &#123;</span><br><span class="line">                charas.add(gattCharacteristic);</span><br><span class="line">                HashMap&lt;String, String&gt; currentCharaData =</span><br><span class="line">                        <span class="keyword">new</span> HashMap&lt;String, String&gt;();</span><br><span class="line">                uuid = gattCharacteristic.getUuid().toString();</span><br><span class="line">                currentCharaData.put(</span><br><span class="line">                        LIST_NAME, SampleGattAttributes.lookup(uuid,</span><br><span class="line">                                unknownCharaString));</span><br><span class="line">                currentCharaData.put(LIST_UUID, uuid);</span><br><span class="line">                gattCharacteristicGroupData.add(currentCharaData);</span><br><span class="line">            &#125;</span><br><span class="line">            mGattCharacteristics.add(charas);</span><br><span class="line">            gattCharacteristicData.add(gattCharacteristicGroupData);</span><br><span class="line">         &#125;</span><br><span class="line">    ...</span><br><span class="line">    &#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="Receiving-GATT-Notification（接收GATT通知）"><a href="#Receiving-GATT-Notification（接收GATT通知）" class="headerlink" title="Receiving GATT Notification（接收GATT通知）"></a>Receiving GATT Notification（接收GATT通知）</h1><p>BLE应用程序要求在设备的某个指定characteristic改变的时候接收到通知是很常见。下面的代码片段展示了如何通过使用setCharacteristicNotification()为一个characteristic设置通知：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> BluetoothGatt mBluetoothGatt;</span><br><span class="line">BluetoothGattCharacteristic characteristic;</span><br><span class="line"><span class="keyword">boolean</span> enabled;</span><br><span class="line">...</span><br><span class="line">mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);</span><br><span class="line">...</span><br><span class="line">BluetoothGattDescriptor descriptor = characteristic.getDescriptor(</span><br><span class="line">        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));</span><br><span class="line">descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);</span><br><span class="line">mBluetoothGatt.writeDescriptor(descriptor);</span><br></pre></td></tr></table></figure>
<p>一旦为一个characteristic启用了通知，当远程设备上的characteristic改变的时候就会触发onCharacteristicChanged()方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="comment">// Characteristic notification</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCharacteristicChanged</span><span class="params">(BluetoothGatt gatt,</span></span></span><br><span class="line"><span class="function"><span class="params">        BluetoothGattCharacteristic characteristic)</span> </span>&#123;</span><br><span class="line">    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="Closing-the-Client-App（关闭客户端应用程序）"><a href="#Closing-the-Client-App（关闭客户端应用程序）" class="headerlink" title="Closing the Client App（关闭客户端应用程序）"></a>Closing the Client App（关闭客户端应用程序）</h1><p>一旦你的应用程序使用完BLE设备，你应该调用close()方法，这样系统才能适当释放占用的资源：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">close</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (mBluetoothGatt == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    mBluetoothGatt.close();</span><br><span class="line">    mBluetoothGatt = <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>原文地址:<a href="http://developer.android.com/guide/topics/connectivity/bluetooth-le.html" target="_blank" rel="noopener">http://developer.android.com/guide/topics/connectivity/bluetooth-le.html</a></p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color5">bluetooth</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/647d650a.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-android-windowSoftInputMode详解" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/c84ead9.html">android:windowSoftInputMode详解</a>
    </h1>
  

        
        <a href="/post/c84ead9.html" class="archive-article-date">
  	<time datetime="2017-12-07T03:25:56.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2017-12-07</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>Activity 的主窗口与包含屏幕软键盘的窗口的交互方式。 该属性的设置影响两个方面：</p>
<ul>
<li>当 Activity 成为用户注意的焦点时软键盘的状态 — 隐藏还是可见。</li>
<li>对 Activity 主窗口所做的调整 — 是否将其尺寸调小以为软键盘腾出空间，或者当窗口部分被软键盘遮挡时是否平移其内容以使当前焦点可见。</li>
</ul>
<p>该设置必须是下表所列的值之一，或者是一个“state…”值加上一个“adjust…”值的组合。 在任一组中设置多个值（例如，多个“state…”值）都会产生未定义结果。各值之间使用垂直条 (|) 分隔。 例如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;activity android:windowSoftInputMode=&quot;stateVisible|adjustResize&quot; . . . &gt;</span><br></pre></td></tr></table></figure>
<p>此处设置的值（<code>stateUnspecified</code>和<code>adjustUnspecified</code>除外）替换主题中设置的值。</p>
<ul>
<li><p><code>stateUnspecified</code><br>  不指定软键盘的状态（隐藏还是可见）。<br>将由系统选择合适的状态，或依赖主题中的设置。这是对软键盘行为的默认设置。</p>
</li>
<li><p><code>stateUnchanged</code><br>  当 Activity 转至前台时保留软键盘最后所处的任何状态，无论是可见还是隐藏。</p>
</li>
<li><code>stateHidden</code><br>  当用户选择 Activity 时 — 也就是说，当用户确实是向前导航到 Activity，而不是因离开另一 Activity 而返回时 — 隐藏软键盘。</li>
<li><code>stateAlwaysHidden</code><br>  当 Activity 的主窗口有输入焦点时始终隐藏软键盘。</li>
<li><code>stateVisible</code><br>  在正常的适宜情况下（当用户向前导航到 Activity 的主窗口时）显示软键盘。</li>
<li><code>stateAlwaysVisible</code><br>  当用户选择 Activity 时 — 也就是说，当用户确实是向前导航到 Activity，而不是因离开另一 Activity 而返回时 — 显示软键盘。</li>
<li><code>adjustUnspecified</code><pre><code>不指定 Activity 的主窗口是否调整尺寸以为软键盘腾出空间，或者窗口内容是否进行平移以在屏幕上显露当前焦点。 系统会根据窗口的内容是否存在任何可滚动其内容的布局视图来自动选择其中一种模式。 如果存在这样的视图，窗口将进行尺寸调整，前提是可通过滚动在较小区域内看到窗口的所有内容。这是对主窗口行为的默认设置。
</code></pre></li>
<li><code>adjustResize</code><br>  始终调整 Activity 主窗口的尺寸来为屏幕上的软键盘腾出空间。</li>
<li><code>adjustPan</code><br>  不调整 Activity 主窗口的尺寸来为软键盘腾出空间， 而是自动平移窗口的内容，使当前焦点永远不被键盘遮盖，让用户始终都能看到其输入的内容。 这通常不如尺寸调正可取，因为用户可能需要关闭软键盘以到达被遮盖的窗口部分或与这些部分进行交互。</li>
</ul>
<p>该属性是在 API 级别 3 引入的。</p>
<h3 id="引入的版本："><a href="#引入的版本：" class="headerlink" title="引入的版本："></a>引入的版本：</h3><p>API 级别 1，为 <code>noHistory</code> 和 <code>windowSoftInputMode</code> 之外的所有属性引入，这两个属性则是在 API 级别 3 中增加。</p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/c84ead9.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-Android-Handler机制详解" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/fecfb901.html">Android Handler机制详解</a>
    </h1>
  

        
        <a href="/post/fecfb901.html" class="archive-article-date">
  	<time datetime="2017-12-05T04:51:08.773Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2017-12-05</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p><code>Handler</code>是Android的消息机制，他能够很轻松的在线程间传递数据。由于Android开发规范的限制，我们不能在主线程执行耗时操作（如网络，IO操作等），不能在子线程更新UI，所以<code>Handler</code>大部分用来在耗时操作与更新UI之间切换。这让很多人误以为<code>Handler</code>就是用来更新UI的，其实这只是它的一小部分应用。</p>
<h3 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h3><p>我相信大多数人对<code>Handler</code>的用法已经烂熟于心了，这篇文章不会去探讨<code>Handler</code>的使用，而是着重从源码上分析<code>Handler</code>的运行机制。</p>
<p>想要了解<code>Handler</code>的运行机制，我们需要了解 <code>MessageQueue</code> ，<code>Message</code>，<code>Looper</code> 这几个类。</p>
<ul>
<li><code>MessageQueue</code> 的意思就是消息队列，它存储了我们需要用来处理的消息<code>Message</code>。</li>
<li><code>Message</code>是消息类，内部存在一个<code>Bundle</code>对象和几个<code>public</code>字段存储数据，<code>MessageQueue</code>作为一个消息队列不能自己处理消息，所以需要用到<code>Looper</code>。</li>
<li><code>Looper</code>是一个循环装置，他负责从不断从<code>MessageQueue</code>里取出<code>Message</code>，然后回调给<code>Handler</code>的<code>handleMessage</code>来执行具体操作。</li>
<li><code>Handler</code>在这里面充当的角色更像是一个辅助类，它让我们不用关系<code>MessageQueue</code>和<code>Looper</code>的具体细节，只需要关系如何发送消息和回调的处理就行了。</li>
</ul>
<p>上面讲了几个关键类在<code>Handler</code>运行机制中的职责，相对大家对Handler机制有个粗略的了解。</p>
<p>我相信各位看官在阅读这篇文章前都是带着问题的，我们将通过问题来解答大家的疑惑。</p>
<h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><h4 id="Looper"><a href="#Looper" class="headerlink" title="Looper"></a>Looper</h4><p>在分析<code>Looper</code>之前，我们还需要知道<code>ThreadLocal</code>这个类，如果对<code>ThreadLocal</code>还不太了解，可以去看我的另一篇文章<a href="/2017/10/10/ThreadLocal详解/">《ThreadLocal详解》</a>。</p>
<h5 id="Looper是如何创建？"><a href="#Looper是如何创建？" class="headerlink" title="Looper是如何创建？"></a>Looper是如何创建？</h5><p><code>Handler</code>执行的线程和它持有的<code>Looper</code>有关。每个<code>Thread</code>都可以创建唯一的Looper对象。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//为当前线程创建Looper对象的方法。</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">prepare</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       prepare(<span class="keyword">true</span>);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">prepare</span><span class="params">(<span class="keyword">boolean</span> quitAllowed)</span> </span>&#123;</span><br><span class="line">   <span class="comment">//使用ThreadLocal来存储当前线程的Looper对象，这保证了每个线程有且仅有一个Looper对象。</span></span><br><span class="line">   <span class="comment">//这里做了非空判断，所以在同一个线程prepare方法是不允许被调用两次的</span></span><br><span class="line">   <span class="comment">//第一次创建好的Looper对象不会被覆盖，它是唯一的。</span></span><br><span class="line">       <span class="keyword">if</span> (sThreadLocal.get() != <span class="keyword">null</span>) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Only one Looper may be created per thread"</span>);</span><br><span class="line">       &#125;</span><br><span class="line">       sThreadLocal.set(<span class="keyword">new</span> Looper(quitAllowed));</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<p>那么主线程的<code>Looper</code>对象是怎么创建的呢？</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">prepareMainLooper</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="comment">//其实主线程创建Looper和其他线程没有区别，也是调用prepare()。</span></span><br><span class="line">        prepare(<span class="keyword">false</span>);</span><br><span class="line">        <span class="keyword">synchronized</span> (Looper.class) &#123;</span><br><span class="line">            <span class="keyword">if</span> (sMainLooper != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"The main Looper has already been prepared."</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//但是Looper用sMainLooper这个静态变量将主线程的Looper对象存储了起来</span></span><br><span class="line">            <span class="comment">//可以通过getMainLooper()获取，存储MainLooper其实非常有作用，下面会讲到。</span></span><br><span class="line">            sMainLooper = myLooper();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Looper <span class="title">getMainLooper</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (Looper.class) &#123;</span><br><span class="line">            <span class="keyword">return</span> sMainLooper;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<h5 id="Looper是如何从MessageQueue取出消息并分发的？"><a href="#Looper是如何从MessageQueue取出消息并分发的？" class="headerlink" title="Looper是如何从MessageQueue取出消息并分发的？"></a>Looper是如何从MessageQueue取出消息并分发的？</h5><p>Looper分发消息的主要逻辑在loop方法里</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Run the message queue in this thread. Be sure to call</span></span><br><span class="line"><span class="comment">    * &#123;<span class="doctag">@link</span> #quit()&#125; to end the loop.</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       <span class="keyword">final</span> Looper me = myLooper();</span><br><span class="line">       <span class="comment">//保证当前线程必须有Looper对象，如果没有则抛出异常，调用Looper.loop()之前应该先调用Looper.prepare().</span></span><br><span class="line">       <span class="keyword">if</span> (me == <span class="keyword">null</span>) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"No Looper; Looper.prepare() wasn't called on this thread."</span>);</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="comment">//Looper需要不断从MessageQueue中取出消息，所以它持有MessageQueue对象</span></span><br><span class="line">       <span class="keyword">final</span> MessageQueue queue = me.mQueue;</span><br><span class="line"></span><br><span class="line">       <span class="comment">// Make sure the identity of this thread is that of the local process,</span></span><br><span class="line">       <span class="comment">// and keep track of what that identity token actually is.</span></span><br><span class="line">       Binder.clearCallingIdentity();</span><br><span class="line">       <span class="keyword">final</span> <span class="keyword">long</span> ident = Binder.clearCallingIdentity();</span><br><span class="line"></span><br><span class="line">       <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">          <span class="comment">//这里开始执行死循环，queue通过调用next方法来取出下一个消息。</span></span><br><span class="line">          <span class="comment">//很多人很疑惑死循环不会相当耗费性能吗，如果没有那么多消息怎么办？</span></span><br><span class="line">          <span class="comment">//其实当没有消息的时候，next方法会阻塞在这里，不会往下执行了，性能问题不存在。</span></span><br><span class="line">           Message msg = queue.next(); <span class="comment">// might block</span></span><br><span class="line">           <span class="keyword">if</span> (msg == <span class="keyword">null</span>) &#123;</span><br><span class="line">               <span class="comment">// No message indicates that the message queue is quitting.</span></span><br><span class="line">               <span class="comment">//这里满足了死循环跳出的条件，即取出的消息为null</span></span><br><span class="line">               <span class="comment">//没有消息next不是会阻塞吗，怎么会返回null呢？</span></span><br><span class="line">               <span class="comment">//其实只有MessageQueue停止的时候（调用quit方法），才会返回null</span></span><br><span class="line">               <span class="comment">//MessageQueue停止后，调用next返回null，且不再接受新消息，下面还有详细介绍。</span></span><br><span class="line">               <span class="keyword">return</span>;</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="comment">// This must be in a local variable, in case a UI event sets the logger</span></span><br><span class="line">           Printer logging = me.mLogging;</span><br><span class="line">           <span class="keyword">if</span> (logging != <span class="keyword">null</span>) &#123;</span><br><span class="line">               logging.println(<span class="string">"&gt;&gt;&gt;&gt;&gt; Dispatching to "</span> + msg.target + <span class="string">" "</span> +</span><br><span class="line">                       msg.callback + <span class="string">": "</span> + msg.what);</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="comment">//这里的msg.target是Handler对象，分发消息到Handler去执行。</span></span><br><span class="line">           <span class="comment">//有人问主线程可以创建这么多Handler，怎么保证这个Handler发送的消息不会跑到其它Handler去执行呢？</span></span><br><span class="line">           <span class="comment">//那是因为在发送Message时，他会绑定发送的Handler，在此处分发消息时，也只会回调发送该条消息的Handler。</span></span><br><span class="line">           <span class="comment">//那么分发消息具体在哪个线程执行呢？</span></span><br><span class="line">           <span class="comment">//我觉得这个不该问，那当然是当前方法在哪个线程调用就在哪个线程执行啦。</span></span><br><span class="line">           msg.target.dispatchMessage(msg);</span><br><span class="line"></span><br><span class="line">           <span class="keyword">if</span> (logging != <span class="keyword">null</span>) &#123;</span><br><span class="line">               logging.println(<span class="string">"&lt;&lt;&lt;&lt;&lt; Finished to "</span> + msg.target + <span class="string">" "</span> + msg.callback);</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">           <span class="comment">// Make sure that during the course of dispatching the</span></span><br><span class="line">           <span class="comment">// identity of the thread wasn't corrupted.</span></span><br><span class="line">           <span class="keyword">final</span> <span class="keyword">long</span> newIdent = Binder.clearCallingIdentity();</span><br><span class="line">           <span class="keyword">if</span> (ident != newIdent) &#123;</span><br><span class="line">               Log.wtf(TAG, <span class="string">"Thread identity changed from 0x"</span></span><br><span class="line">                       + Long.toHexString(ident) + <span class="string">" to 0x"</span></span><br><span class="line">                       + Long.toHexString(newIdent) + <span class="string">" while dispatching to "</span></span><br><span class="line">                       + msg.target.getClass().getName() + <span class="string">" "</span></span><br><span class="line">                       + msg.callback + <span class="string">" what="</span> + msg.what);</span><br><span class="line">           &#125;</span><br><span class="line"></span><br><span class="line">       <span class="comment">//这里对Message对象进行回收，会清空所有之前Message设置的数据。</span></span><br><span class="line">       <span class="comment">//正是因为Message有回收机制，我们在创建消息的时候应该优先选择Message.obtain(). </span></span><br><span class="line">       <span class="comment">//如果发送的消息足够多，Message缓存的Message对象不够了，obtain内部会调用new Message()创建一个新的对象。</span></span><br><span class="line">           msg.recycleUnchecked();</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<h5 id="Looper-分发的消息在哪个线程执行？"><a href="#Looper-分发的消息在哪个线程执行？" class="headerlink" title="Looper 分发的消息在哪个线程执行？"></a>Looper 分发的消息在哪个线程执行？</h5><p>先给大家展示一段<code>Looper</code>文档上的示例代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LooperThread</span> <span class="keyword">extends</span> <span class="title">Thread</span> </span>&#123;</span><br><span class="line">   <span class="keyword">public</span> Handler mHandler;</span><br><span class="line">  </span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Looper.prepare(); <span class="comment">//创建LooperThread的Looper对象</span></span><br><span class="line">  </span><br><span class="line">        mHandler = <span class="keyword">new</span> Handler() &#123;</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleMessage</span><span class="params">(Message msg)</span> </span>&#123;</span><br><span class="line">              <span class="comment">//处理发送过来的消息</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    </span><br><span class="line">        Looper.loop(); <span class="comment">//开始循环消息队列</span></span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面这段代码相信很多人都写过，这是一段在子线程创建Handler的案例，其中<code>handleMessage</code>所执行的线程为<code>LooperThread</code>，因为<code>Looper.loop()</code>执行在<code>LooperThread</code>的<code>run</code>方法里。可以在其他线程通过<code>mHandler</code>发送消息到<code>LooperThread</code></p>
<h5 id="如果不调用Looper-prepare-直接new-Handler-会怎么样呢？"><a href="#如果不调用Looper-prepare-直接new-Handler-会怎么样呢？" class="headerlink" title="如果不调用Looper.prepare()直接new Handler()会怎么样呢？"></a>如果不调用<code>Looper.prepare()</code>直接<code>new Handler()</code>会怎么样呢？</h5><p>我们可以查看<code>Handler</code>的源码看看无参构造是如何运行的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">Handler</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="comment">//调用两参构造</span></span><br><span class="line">    <span class="keyword">this</span>(<span class="keyword">null</span>, <span class="keyword">false</span>);</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">Handler</span><span class="params">(Callback callback, <span class="keyword">boolean</span> async)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (FIND_POTENTIAL_LEAKS) &#123;</span><br><span class="line">        <span class="keyword">final</span> Class&lt;? extends Handler&gt; klass = getClass();</span><br><span class="line">        <span class="keyword">if</span> ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &amp;&amp;</span><br><span class="line">                (klass.getModifiers() &amp; Modifier.STATIC) == <span class="number">0</span>) &#123;</span><br><span class="line">            Log.w(TAG, <span class="string">"The following Handler class should be static or leaks might occur: "</span> +</span><br><span class="line">                klass.getCanonicalName());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="comment">//获取当前线程的Looper，如果不创建Looper会抛出异常。</span></span><br><span class="line"><span class="comment">//主线程我也没看到有调用Looper.prepare()啊，怎么在主线程不会抛异常呢？这个看下一个问题。</span></span><br><span class="line">    mLooper = Looper.myLooper();</span><br><span class="line">    <span class="keyword">if</span> (mLooper == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(</span><br><span class="line">            <span class="string">"Can't create handler inside thread that has not called Looper.prepare()"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    mQueue = mLooper.mQueue;</span><br><span class="line">    mCallback = callback;</span><br><span class="line">    mAsynchronous = async;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="主线程的Looper对象在哪里创建的？"><a href="#主线程的Looper对象在哪里创建的？" class="headerlink" title="主线程的Looper对象在哪里创建的？"></a>主线程的Looper对象在哪里创建的？</h5><p>从上一个问题可以看出如果不调用<code>Looper.prepare()</code>直接<code>new Handler()</code>就会抛出异常` </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(</span><br><span class="line">            <span class="string">"Can't create handler inside thread that has not called Looper.prepare()"</span>);`</span><br></pre></td></tr></table></figure>
<p>那么主线程的<code>Looper</code>在哪里创建的呢？首先它是创建了的，因为<code>Looper.getMainLooper() != null</code>，其实<code>MainLooper</code>创建的时间比我们想象的早，它在<code>ActivityThread</code>类里面，<code>ActivityThread</code>是<code>Android</code>的启动类，<code>main</code>方法就在里面（如果有人问你Android有没有main方法，你应该知道怎么回答了吧），而<code>MainLooper</code>就是在<code>main</code>方法里面创建的。</p>
<p>上代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//android.app.ActivityThread</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">ActivityThread</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">         </span><br><span class="line">        SamplingProfilerIntegration.start();</span><br><span class="line">    </span><br><span class="line">        <span class="comment">// CloseGuard defaults to true and can be quite spammy.  We</span></span><br><span class="line">        <span class="comment">// disable it here, but selectively enable it later (via</span></span><br><span class="line">        <span class="comment">// StrictMode) on debug builds, but using DropBox, not logs.</span></span><br><span class="line">        CloseGuard.setEnabled(<span class="keyword">false</span>);</span><br><span class="line">    </span><br><span class="line">        Environment.initForCurrentUser();</span><br><span class="line">    </span><br><span class="line">        <span class="comment">// Set the reporter for event logging in libcore</span></span><br><span class="line">        EventLogger.setReporter(<span class="keyword">new</span> EventLoggingReporter());</span><br><span class="line">    </span><br><span class="line">        Security.addProvider(<span class="keyword">new</span> AndroidKeyStoreProvider());</span><br><span class="line">    </span><br><span class="line">        <span class="comment">// Make sure TrustedCertificateStore looks in the right place for CA certificates</span></span><br><span class="line">        <span class="keyword">final</span> File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());</span><br><span class="line">        TrustedCertificateStore.setDefaultUserDirectory(configDir);</span><br><span class="line">    </span><br><span class="line">        Process.setArgV0(<span class="string">"&lt;pre-initialized&gt;"</span>);</span><br><span class="line">    </span><br><span class="line">        <span class="comment">//注意这里，这里创建了主线程的Looper</span></span><br><span class="line">        Looper.prepareMainLooper();</span><br><span class="line">    </span><br><span class="line">        ActivityThread thread = <span class="keyword">new</span> ActivityThread();</span><br><span class="line">        thread.attach(<span class="keyword">false</span>);</span><br><span class="line">    </span><br><span class="line">        <span class="keyword">if</span> (sMainThreadHandler == <span class="keyword">null</span>) &#123;</span><br><span class="line">            sMainThreadHandler = thread.getHandler();</span><br><span class="line">        &#125;</span><br><span class="line">    </span><br><span class="line">        AsyncTask.init();</span><br><span class="line">    </span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">false</span>) &#123;</span><br><span class="line">            Looper.myLooper().setMessageLogging(<span class="keyword">new</span></span><br><span class="line">                    LogPrinter(Log.DEBUG, <span class="string">"ActivityThread"</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//开启消息循环</span></span><br><span class="line">        Looper.loop();</span><br><span class="line">    </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Main thread loop unexpectedly exited"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="MainLooper可以用来做什么"><a href="#MainLooper可以用来做什么" class="headerlink" title="MainLooper可以用来做什么"></a>MainLooper可以用来做什么</h5><h6 id="判断当前线程是否为主线程"><a href="#判断当前线程是否为主线程" class="headerlink" title="判断当前线程是否为主线程"></a>判断当前线程是否为主线程</h6><p>因为Looper是在某一线程唯一的，那么可以在么做。如果</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isMainThread</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">//如果当前线程的Looper和MainLooper是同一个对象，那么可以认为当前线程是主线程</span></span><br><span class="line">        <span class="keyword">return</span> Looper.myLooper() == Looper.getMainLooper() ;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>但是也有人说下面这样也可以</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isMainThread</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这个方法其实是不准确的，线程的名称是可以随便更改的。</span></span><br><span class="line">    <span class="keyword">return</span> Thread.currentThread().getName().equals(<span class="string">"main"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>所以用<code>Looper</code>来判断主线程是很好的做法</p>
<h6 id="创建运行在主线程的Handler"><a href="#创建运行在主线程的Handler" class="headerlink" title="创建运行在主线程的Handler"></a>创建运行在主线程的Handler</h6><p><code>Handler</code>除了有无参构造，还有一个可以传入<code>Looper</code>的构造。通过指定<code>Looper</code>，可以在任意地方创建运行在主线程的<code>Handler</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">WorkThread</span> <span class="keyword">extends</span> <span class="title">Thread</span></span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Handler mHandler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.run();</span><br><span class="line">        mHandler = <span class="keyword">new</span> Handler(Looper.getMainLooper()) &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleMessage</span><span class="params">(Message msg)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">super</span>.handleMessage(msg);</span><br><span class="line">                <span class="comment">//运行在主线程</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        mHandler.sendEmptyMessage(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h5 id="Looper的quit方法和quitSafely方法有什么区别"><a href="#Looper的quit方法和quitSafely方法有什么区别" class="headerlink" title="Looper的quit方法和quitSafely方法有什么区别"></a>Looper的quit方法和quitSafely方法有什么区别</h5><p>下面是<code>Looper</code>两个方法的源码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">quit</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mQueue.quit(<span class="keyword">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">quitSafely</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mQueue.quit(<span class="keyword">true</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看出实际上是调用的<code>MessageQueue</code>的<code>quit</code>方法<br>下面是<code>MessageQueue</code>的源码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//android.os.MessageQueue</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">quit</span><span class="params">(<span class="keyword">boolean</span> safe)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!mQuitAllowed) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"Main thread not allowed to quit."</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (mQuitting) &#123;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            mQuitting = <span class="keyword">true</span>;</span><br><span class="line">            <span class="comment">//如果调用的是quitSafely运行removeAllFutureMessagesLocked，否则removeAllMessagesLocked。</span></span><br><span class="line">            <span class="keyword">if</span> (safe) &#123;</span><br><span class="line">            <span class="comment">//该方法只会清空MessageQueue消息池中所有的延迟消息，</span></span><br><span class="line">            <span class="comment">//并将消息池中所有的非延迟消息派发出去让Handler去处理，</span></span><br><span class="line">            <span class="comment">//quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。</span></span><br><span class="line">                removeAllFutureMessagesLocked();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">//该方法的作用是把MessageQueue消息池中所有的消息全部清空，</span></span><br><span class="line">            <span class="comment">//无论是延迟消息（延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息）还是非延迟消息。</span></span><br><span class="line">                removeAllMessagesLocked();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// We can assume mPtr != 0 because mQuitting was previously false.</span></span><br><span class="line">            nativeWake(mPtr);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<p>无论是调用了<code>quit</code>方法还是<code>quitSafely</code>方法，<code>MessageQueue</code>将不再接收新的<code>Message</code>，此时消息循环就结束，<code>MessageQueued</code>的<code>next</code>方法将返回<code>null</code>，结束<code>loop()</code>的死循环.这时候再通过<code>Handler</code>调用<code>sendMessage</code>或<code>post</code>等方法发送消息时均返回<code>false</code>，表示消息没有成功放入消息队列<code>MessageQueue</code>中，因为消息队列已经退出了。</p>
<h4 id="Message"><a href="#Message" class="headerlink" title="Message"></a>Message</h4><h5 id="Message-obtain-和new-Message-如何选择"><a href="#Message-obtain-和new-Message-如何选择" class="headerlink" title="Message.obtain()和new Message()如何选择"></a>Message.obtain()和new Message()如何选择</h5><p><code>Message</code>提供了<code>obtain</code>等多个重载的方法来创建<code>Message</code>对象，那么这种方式和直接<code>new</code>该如何选择。下面看看<code>obtain</code>的代码。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Message <span class="title">obtain</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (sPoolSync) &#123;</span><br><span class="line">        <span class="keyword">if</span> (sPool != <span class="keyword">null</span>) &#123;</span><br><span class="line">            Message m = sPool;</span><br><span class="line">            sPool = m.next;</span><br><span class="line">            m.next = <span class="keyword">null</span>;</span><br><span class="line">            m.flags = <span class="number">0</span>; <span class="comment">// clear in-use flag</span></span><br><span class="line">            sPoolSize--;</span><br><span class="line">            <span class="keyword">return</span> m;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> Message(); <span class="comment">//只有当从对象池里取不出Message才去new</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">recycleUnchecked</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">//清除所有使用过的痕迹</span></span><br><span class="line">        flags = FLAG_IN_USE;</span><br><span class="line">        what = <span class="number">0</span>;</span><br><span class="line">        arg1 = <span class="number">0</span>;</span><br><span class="line">        arg2 = <span class="number">0</span>;</span><br><span class="line">        obj = <span class="keyword">null</span>;</span><br><span class="line">        replyTo = <span class="keyword">null</span>;</span><br><span class="line">        sendingUid = -<span class="number">1</span>;</span><br><span class="line">        when = <span class="number">0</span>;</span><br><span class="line">        target = <span class="keyword">null</span>;</span><br><span class="line">        callback = <span class="keyword">null</span>;</span><br><span class="line">        data = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//回收到对象池</span></span><br><span class="line">        <span class="keyword">synchronized</span> (sPoolSync) &#123;</span><br><span class="line">            <span class="keyword">if</span> (sPoolSize &lt; MAX_POOL_SIZE) &#123;</span><br><span class="line">                next = sPool;</span><br><span class="line">                sPool = <span class="keyword">this</span>;</span><br><span class="line">                sPoolSize++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<p>从上面代码可以看出，通过<code>obtain</code>方法是从对象池取，而<code>new</code>是创建了一个新的对象。我们应该使用<code>obtain</code>来创建<code>Message</code>对象，每次使用完后都会自动进行回收，节省内存。</p>
<p>未完待续……</p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Handler</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/fecfb901.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-Android应用内安装App的几种姿势" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/7bdf5ef8.html">Android应用内安装App的几种姿势</a>
    </h1>
  

        
        <a href="/post/7bdf5ef8.html" class="archive-article-date">
  	<time datetime="2017-10-27T09:16:58.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2017-10-27</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h4 id="调用系统安装程序"><a href="#调用系统安装程序" class="headerlink" title="调用系统安装程序"></a>调用系统安装程序</h4><p>这种方式最为简单，只需要调起系统界面即可。看代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 调用系统安装界面</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> apkFile</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">startInstallActivity</span><span class="params">(Context context, File apkFile)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (apkFile == <span class="keyword">null</span> || !apkFile.exists()) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    Intent intent = <span class="keyword">new</span> Intent(Intent.ACTION_VIEW);</span><br><span class="line">    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);</span><br><span class="line">    intent.setDataAndType(Uri.parse(<span class="string">"file://"</span> + apkFile.toString()), <span class="string">"application/vnd.android.package-archive"</span>);</span><br><span class="line">    context.startActivity(intent);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<hr>
<h4 id="调用系统pm命令"><a href="#调用系统pm命令" class="headerlink" title="调用系统pm命令"></a>调用系统pm命令</h4><p>这种方式是在通过终端执行<em>pm</em>命令来实现 </p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pm install -r [filepath]</span><br></pre></td></tr></table></figure>
<p>其中<code>-r</code>代表覆盖安装。<br>这种方式可以实现静默安装，不需要调起系统界面。但是有一个前提，就是需要<em>root</em>权限，没有去<em>root</em>权限，<em>pm</em>命令不能执行。获取<em>root</em>权限不是本文范畴，请自行百度或者google。</p>
<p>满足前提的情况下，我们可能用到以下方法。</p>
<ul>
<li>判断设备是否有root权限,通过执行su命令是否正确来检查设备是否被root</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 检查设备是否被root</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isRoot</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        Process process = Runtime.getRuntime().exec(<span class="string">"su"</span>);</span><br><span class="line">        process.getOutputStream().write(<span class="string">"exit\n"</span>.getBytes());</span><br><span class="line">        process.getOutputStream().flush();</span><br><span class="line">        <span class="keyword">int</span> i = process.waitFor();</span><br><span class="line">        <span class="keyword">if</span> (<span class="number">0</span> == i) &#123;</span><br><span class="line">            process = Runtime.getRuntime().exec(<span class="string">"su"</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ul>
<li>执行终端命令</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 执行终端命令</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> cmd</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> IOException</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> InterruptedException</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">execRootCmdSilent</span><span class="params">(String cmd)</span> <span class="keyword">throws</span> IOException, InterruptedException </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> result = -<span class="number">1</span>;</span><br><span class="line">        DataOutputStream dos = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Process p = Runtime.getRuntime().exec(<span class="string">"su"</span>);</span><br><span class="line">            dos = <span class="keyword">new</span> DataOutputStream(p.getOutputStream());</span><br><span class="line">            Log.i(TAG, cmd);</span><br><span class="line">            dos.writeBytes(cmd + <span class="string">"\n"</span>);</span><br><span class="line">            dos.flush();</span><br><span class="line">            dos.writeBytes(<span class="string">"exit\n"</span>);</span><br><span class="line">            dos.flush();</span><br><span class="line">            p.waitFor();</span><br><span class="line">            result = p.exitValue();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (dos != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    dos.close();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<ul>
<li>安装apk，在终端执行 <code>pm install -r filePath</code></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">installApk</span><span class="params">(String filePath)</span> <span class="keyword">throws</span> IOException, InterruptedException </span>&#123;</span><br><span class="line">   <span class="keyword">if</span> (TextUtils.isEmpty(filePath) || !<span class="keyword">new</span> File(filePath).exists()) <span class="keyword">return</span>;</span><br><span class="line">   execRootCmdSilent(<span class="string">"pm install -r "</span> + filePath);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<hr>
<h4 id="反射PackageManager的installPackage方法"><a href="#反射PackageManager的installPackage方法" class="headerlink" title="反射PackageManager的installPackage方法"></a>反射<em>PackageManager</em>的<em>installPackage</em>方法</h4><p>通过查看查看系统安装程序的源码，可以看到系统安装程序实际上是调用<em>PackageManager</em>的<em>installPackage</em>方法来实现安装的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@deprecated</span> replaced by &#123;<span class="doctag">@link</span> PackageInstaller&#125;</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Deprecated</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">installPackage</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">            Uri packageURI, //APK地址</span></span></span><br><span class="line"><span class="function"><span class="params">            IPackageInstallObserver observer, //安装回调</span></span></span><br><span class="line"><span class="function"><span class="params">            @InstallFlags <span class="keyword">int</span> flags,</span></span></span><br><span class="line"><span class="function"><span class="params">            String installerPackageName)</span></span>; <span class="comment">//被安装APK的包名</span></span><br></pre></td></tr></table></figure>
<ul>
<li><p><em>installPackage</em>方法虽然是<em>public</em>修饰的，但是同时被<em>@hide</em>了，所以应用程序无法直接调用，这就需要用到反射机制了。</p>
</li>
<li><p>调用这个方法需要系统权限，所以需要用系统签名对apk打包，具体操作可以参考<a href="http://connorlin.github.io/2016/04/27/让Android-Studio支持系统签名&#40;证书&#41;" target="_blank" rel="noopener">这篇文章</a></p>
</li>
<li><p><em>IPackageInstallObserver</em>是一个<em>AIDL</em>接口，所以我们需要用到<em>IPackageInstallObserver.aidl</em> 这个文件。你可以</p>
<ul>
<li><p>直接从系统源码拷贝，该文件位于<code>/frameworks/base/core/java/android/content/pm</code></p>
</li>
<li><p>或者拷贝我的，在你的项目下创建<code>app/src/main/aidl/android/content/pm/IPackageInstallObserver.aidl</code>文件，然后将下面的代码复制进去</p>
</li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">package</span> android.content.pm;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * API for installation callbacks from the Package Manager.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">oneway <span class="class"><span class="keyword">interface</span> <span class="title">IPackageInstallObserver</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">packageInstalled</span><span class="params">(in String packageName, <span class="keyword">int</span> returnCode)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>当上述都做好了之后。</p>
<ul>
<li>调用安装的方法</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//installPackage方法名</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String INSTALL_METHOD = <span class="string">"installPackage"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//InstallFlags</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FORWARD_LOCK = <span class="number">0x00000001</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_REPLACE_EXISTING = <span class="number">0x00000002</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_ALLOW_TEST = <span class="number">0x0000000</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_EXTERNAL = <span class="number">0x00000008</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_INTERNAL = <span class="number">0x00000010</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FROM_ADB = <span class="number">0x00000020</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_ALL_USERS = <span class="number">0x00000040</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_ALLOW_DOWNGRADE = <span class="number">0x00000080</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_GRANT_RUNTIME_PERMISSIONS = <span class="number">0x00000100</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FORCE_VOLUME_UUID = <span class="number">0x00000200</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FORCE_PERMISSION_PROMPT = <span class="number">0x00000400</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_EPHEMERAL = <span class="number">0x00000800</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_DONT_KILL_APP = <span class="number">0x00001000</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FORCE_SDK = <span class="number">0x00002000</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DONT_KILL_APP = <span class="number">0x00000001</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">installPackage</span><span class="params">(Context context, File file, IPackageInstallObserver observer)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> NoSuchMethodException, InvocationTargetException, IllegalAccessException </span>&#123;</span><br><span class="line">    PackageManager packageManager = context.getPackageManager();</span><br><span class="line">    Method method = PackageManager.class.getDeclaredMethod(INSTALL_METHOD, Uri.class,</span><br><span class="line">            IPackageInstallObserver.class, <span class="keyword">int</span>.class, String.class);</span><br><span class="line">    method.invoke(packageManager, Uri.fromFile(file), observer, INSTALL_REPLACE_EXISTING | INSTALL_DONT_KILL_APP, GetAppInfo.getAPKPackageName(context, file.getAbsolutePath()));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<ul>
<li>监听安装回调</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">//安装回调</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">PackageInstallObserver</span> <span class="keyword">extends</span> <span class="title">IPackageInstallObserver</span>.<span class="title">Stub</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">packageInstalled</span><span class="params">(String packageName, <span class="keyword">int</span> returnCode)</span> <span class="keyword">throws</span> RemoteException </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(returnCode == INSTALL_SUCCEEDED)&#123;</span><br><span class="line">            <span class="comment">//安装成功</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//returnCode的取值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_SUCCEEDED = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_ALREADY_EXISTS = -<span class="number">1</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_INVALID_APK = -<span class="number">2</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_INVALID_URI = -<span class="number">3</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_INSUFFICIENT_STORAGE = -<span class="number">4</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_DUPLICATE_PACKAGE = -<span class="number">5</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_NO_SHARED_USER = -<span class="number">6</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_UPDATE_INCOMPATIBLE = -<span class="number">7</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -<span class="number">8</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_MISSING_SHARED_LIBRARY = -<span class="number">9</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_REPLACE_COULDNT_DELETE = -<span class="number">10</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_DEXOPT = -<span class="number">11</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_OLDER_SDK = -<span class="number">12</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_CONFLICTING_PROVIDER = -<span class="number">13</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_NEWER_SDK = -<span class="number">14</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_TEST_ONLY = -<span class="number">15</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -<span class="number">16</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_MISSING_FEATURE = -<span class="number">17</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_CONTAINER_ERROR = -<span class="number">18</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_INVALID_INSTALL_LOCATION = -<span class="number">19</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_MEDIA_UNAVAILABLE = -<span class="number">20</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_VERIFICATION_TIMEOUT = -<span class="number">21</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_VERIFICATION_FAILURE = -<span class="number">22</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_PACKAGE_CHANGED = -<span class="number">23</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_UID_CHANGED = -<span class="number">24</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_VERSION_DOWNGRADE = -<span class="number">25</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE = -<span class="number">26</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_NOT_APK = -<span class="number">100</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_BAD_MANIFEST = -<span class="number">101</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -<span class="number">102</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_NO_CERTIFICATES = -<span class="number">103</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -<span class="number">104</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -<span class="number">105</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -<span class="number">106</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -<span class="number">107</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -<span class="number">108</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -<span class="number">109</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_INTERNAL_ERROR = -<span class="number">110</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_USER_RESTRICTED = -<span class="number">111</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_DUPLICATE_PERMISSION = -<span class="number">112</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_NO_MATCHING_ABIS = -<span class="number">113</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> NO_NATIVE_LIBRARIES = -<span class="number">114</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> INSTALL_FAILED_ABORTED = -<span class="number">115</span>;</span><br></pre></td></tr></table></figure>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/7bdf5ef8.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-如何理解Kotlin中属性" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/6585af7e.html">如何理解Kotlin中属性</a>
    </h1>
  

        
        <a href="/post/6585af7e.html" class="archive-article-date">
  	<time datetime="2017-10-27T07:59:12.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2017-10-27</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h2 id="属性的定义"><a href="#属性的定义" class="headerlink" title="属性的定义"></a>属性的定义</h2><p>在<strong>kotlin</strong>中，<strong>var</strong>和<strong>val</strong>是用来声明属性的两个关键字，在<strong>kotlin</strong>官方参考文档上是这么说的。</p>
<blockquote>
<p><strong>Kotlin</strong>的类可以有属性。 属性可以用关键字 <strong>var</strong>声明为可变的，否则使用只读关键字<strong>val</strong>。</p>
</blockquote>
<p>那什么是可变属性，什么又是可读属性呢？我们在问这个问题前可以先回顾一下java对属性的定义。</p>
<blockquote>
<p>属性可以通过<strong>get</strong>、<strong>set</strong>、<strong>is</strong>（可以替代<strong>get</strong>，用在布尔型属性上）方法或遵循特定命名规范的其他方法访问。</p>
</blockquote>
<p>在这里我们可以理解为，在<strong>java</strong>中，对一个字段生成<strong>public</strong>的<strong>set get</strong>方法，那么他就是一个可变（可读可写）属性，仅提供<strong>public get</strong>或者<strong>set</strong>被<strong>private</strong>修饰的就是一个只读属性。</p>
<p>在kotlin中我们是如何定义属性的呢</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>&#123;</span><br><span class="line">    <span class="comment">//定义一个String类型的属性name</span></span><br><span class="line">    var name: String? = <span class="keyword">null</span></span><br><span class="line">    <span class="comment">//定义一个Boolean类型的属性deceased</span></span><br><span class="line">    var deceased: Boolean? = <span class="keyword">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>kotlin和java同属于jvm语言，编译后会生成class文件。我将上面的kotlin代码生成的class再次反编译成java代码，如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>&#123;</span><br><span class="line">   <span class="meta">@Nullable</span></span><br><span class="line">   <span class="keyword">private</span> String name;</span><br><span class="line">   <span class="meta">@Nullable</span></span><br><span class="line">   <span class="keyword">private</span> Boolean deceased = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Nullable</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">getName</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(@Nullable String var1)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">this</span>.name = var1;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Nullable</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> Boolean <span class="title">getDeceased</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.deceased;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setDeceased</span><span class="params">(@Nullable Boolean var1)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">this</span>.deceased = var1;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>看到这里你会发现，反编译后代码和java属性的定义是一毛一样有木有。所以<strong>kotlin</strong>的属性，在<strong>java</strong>代码中可以通过<strong>set get</strong>方法来调用。</p>
<h2 id="只读属性和常量"><a href="#只读属性和常量" class="headerlink" title="只读属性和常量"></a>只读属性和常量</h2><p>有的地方说，<strong>var</strong>是声明变量的，<strong>val</strong>是声明常量的。这个其实不是完全正确，至少<strong>val</strong>在官方参考文档上说的是只读属性，而不是常量。比方说温度，我们可以获取温度的变化，但是不能人为直接改变温度，那么温度就是只读属性，而不是常量。<br>再比如说list的isEmpty属性</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> isEmpty <span class="keyword">get</span>() = <span class="keyword">this</span>.size == <span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>isEmpty属性受到size的改变而改变，但是无法主动去设置isEmpty的值。那么isEmpty就是个只读属性。</p>
<p>在这里要分为几种情况来理解<strong>val</strong>。</p>
<ul>
<li><strong>val</strong>声明的属性被字面量或者表达式赋值的时候</li>
<li><strong>val</strong>声明的属性实现了自定义的<strong>get</strong>方法</li>
</ul>
<p>看下面的例子</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这里的name属性被字面量“zhangsan”赋值了</span></span><br><span class="line">    <span class="keyword">val</span> name: String? = <span class="string">"zhangsan"</span></span><br><span class="line">     <span class="comment">//age属性的值受birthday的影响</span></span><br><span class="line">    <span class="keyword">val</span> age: <span class="built_in">Int</span> <span class="keyword">get</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> Calendar.getInstance().<span class="keyword">get</span>(Calendar.YEAR) - birthday</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">var</span> birthday: <span class="built_in">Int</span> = <span class="number">0</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p> 反编译成<strong>java</strong>代码是这样的</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>&#123;</span><br><span class="line">    <span class="comment">//name属性被字面量赋值，编译后会加上final关键字，成为真正的常量</span></span><br><span class="line">   <span class="meta">@Nullable</span></span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">final</span> String name = <span class="string">"zhangsan"</span>;</span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">int</span> birthday;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Nullable</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">getName</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line">   &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//age属性只实现了get方法，无法通过set方法来改变值，是个只读属性</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">getAge</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> Calendar.getInstance().get(<span class="number">1</span>) - <span class="keyword">this</span>.birthday;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">getBirthday</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.birthday;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setBirthday</span><span class="params">(<span class="keyword">int</span> var1)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">this</span>.birthday = var1;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="什么是变量的幕后字段"><a href="#什么是变量的幕后字段" class="headerlink" title="什么是变量的幕后字段"></a>什么是变量的幕后字段</h2><p>在<strong>kotlin</strong>中可以通过关键字声明属性，那可不可以声明字段呢？它有没有字段呢？<br>答：<strong>kotlin</strong>中不能声明字段，但是<strong>kotlin</strong>中是有字段的，他有一个幕后字段的概念，每一个属性可以有一个幕后字段，也可以没有。</p>
<p>拿上面的例子再讲一遍</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> isEmpty <span class="keyword">get</span>() = <span class="keyword">this</span>.size == <span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>isEmpty的值只和size有关，那么在反编译之后，不会出现如下代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">boolean</span> isEmpty = <span class="keyword">false</span>;</span><br></pre></td></tr></table></figure>
<p>这里可以得出一个结论，属性不一定非得有字段。对<strong>java</strong>属性定义的后半句是这么说的，“<em>或遵循特定命名规范的其他方法访问</em>”，所以关键看方法的定义，比如说在一个类里面有<strong>setWidth()</strong>,<strong>getWidth()</strong>这两个方法。就可以认定为这个类有<strong>width</strong>属性，但是不一定有<strong>width</strong>这个字段。</p>
<p>也就是说这个属性没有幕后字段。</p>
<p>再看下面这个例子</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//name属性没有实现自定义的set get方法</span></span><br><span class="line"><span class="keyword">var</span> name:String? = <span class="literal">null</span>;</span><br><span class="line">......</span><br><span class="line"></span><br><span class="line">name = <span class="string">"zhangsan"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> len = name.length()</span><br></pre></td></tr></table></figure>
<p><strong>name</strong>没有实现自定义的<strong>set get</strong>方法，字面值”zhangsan”需要一个字段来赋值。那么在class里面会生成 </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//这个就是name属性的幕后字段</span></span><br><span class="line"><span class="keyword">private</span> String name = <span class="keyword">null</span>;</span><br></pre></td></tr></table></figure>
<p>那么幕后字段可以用代码访问到么，这个是可以的，使用 <strong>field</strong> 关键字</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> name: String? = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//上面这句代码其实和下面的代码是等价的，没有区别。</span></span><br><span class="line"><span class="keyword">var</span> name: String? = <span class="literal">null</span></span><br><span class="line">   <span class="keyword">set</span>(value) &#123;</span><br><span class="line">       field = value</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">get</span>() &#123;</span><br><span class="line">       <span class="keyword">return</span> field</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
<p>讲到这里我相信大家对幕后字段已经有了一定的了解。</p>
<h2 id="扩展属性"><a href="#扩展属性" class="headerlink" title="扩展属性"></a>扩展属性</h2><p>在kotlin声明一个扩展属性的方法如下。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> Person.city:String <span class="keyword">get</span>() &#123; <span class="keyword">return</span> <span class="string">"city"</span> &#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注：扩展属性只能用生成自定义set get方法来实现，不能直接用字面量来赋值。所以下面的代码是错的</p>
</blockquote>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> Person.city:String = <span class="string">"city"</span></span><br></pre></td></tr></table></figure>
<p>第二句代码是无法编译通过的，那是因为</p>
<blockquote>
<p>扩展是静态解析的.扩展不能真正的修改他们所扩展的类。通过定义一个扩展，你并没有在一个类中插入新成 员， 仅仅是可以通过该类型的变量用点表达式去调用这个新函数,或者属性。由于扩展没有实际的将成员插入类中，因此对扩展属性来说幕后字段是无效的。这就 是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 <strong>get/set</strong> 定义。</p>
</blockquote>
<p>静态解析的结果是</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@NotNull</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String <span class="title">getCity</span><span class="params">(@NotNull Person $receiver)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">"city"</span>;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>所以扩展仅仅是可以通过点语法来调用，并不是真正插入类里面，也不存在幕后字段。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul>
<li>属性可以通过<strong>get</strong>、<strong>set</strong>、<strong>is</strong>（可以替代<strong>get</strong>，用在布尔型属性上）方法或遵循特定命名规范的其他方法访问。</li>
<li>只读属性和常量不是同一个概念，具体看上头。</li>
<li>属性可以有一个幕后字段，也可以没有</li>
<li>扩展是静态解析的，扩展属性是没有幕后字段的</li>
<li>扩展只是能通过点语法调用而已。</li>
</ul>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color2">Kotlin</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/6585af7e.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
    <article id="post-Java转Kotlin-boolean属性的坑" class="article article-type-post  article-index" itemscope itemprop="blogPost">
  <div class="article-inner">
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/post/2d13bd4b.html">Java转Kotlin boolean属性的坑</a>
    </h1>
  

        
        <a href="/post/2d13bd4b.html" class="archive-article-date">
  	<time datetime="2017-10-27T03:59:22.000Z" itemprop="datePublished"><i class="icon-calendar icon"></i>2017-10-27</time>
</a>
        
      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h3 id="踩坑"><a href="#踩坑" class="headerlink" title="踩坑"></a>踩坑</h3><p>假设一个 <code>JavaBean</code> 类中有 <code>boolean</code> 类型的字段叫做 <code>enabled</code><br>它生成的<code>setter</code>/<code>getter</code> 方法将是</p>
<ul>
<li>setter: <code>setEnabled(boolean enabled)</code></li>
<li>getter: <code>isEnabled()</code></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">JavaBean</span></span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">boolean</span> enabled;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isEnabled</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> enabled;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setEnabled</span><span class="params">(<span class="keyword">boolean</span> enabled)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.enabled = enabled;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<p>使用 <code>Android Studio</code> 的 <code>kotlin</code> 插件转换后将生成 <code>isEnabled</code> 属性，而不是 <code>enabled</code> 属性。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JavaBean</span> </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> isEnabled: <span class="built_in">Boolean</span> = <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们知道<code>kotlin</code>编译后也是生成<code>class</code>文件，所以也是可以转成<code>java</code>文件的，通过查看反编译后的结果来学习<code>kotlin</code>不失为一种好方法。</p>
<p>可以在 <code>Android Studio</code> 执行下面的操作将 <code>kotlin</code> 代码重新转成<code>java</code>代码。<br>Tools / Kotlin / Show Kotlin Bytecode / Decompile. </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">JavaBean</span> </span>&#123;</span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">boolean</span> isEnabled;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">isEnabled</span><span class="params">()</span> </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">this</span>.isEnabled;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">setEnabled</span><span class="params">(<span class="keyword">boolean</span> var1)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">this</span>.isEnabled = var1;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到是 <code>isEnabled</code> ，不是 <code>enabled</code>。</p>
<h4 id="可能遇到的问题"><a href="#可能遇到的问题" class="headerlink" title="可能遇到的问题"></a>可能遇到的问题</h4><ul>
<li><p>我在项目中使用的是<strong>Gson</strong>来解析后台返回来的数据。<strong>Gson</strong>是通过反射字段的名称来赋值的，然而数据中是 <code>enbaled</code> ，代码里是 <code>isEnabled</code> ，它将永远不能被赋值，永远为<code>false</code>，解决方法可以是</p>
<ul>
<li>给字段添加 <code>@SerializedName(value = &quot;enbaled&quot;)</code></li>
<li>直接将 <code>isEnabled</code> 改成 <code>enbaled</code>，保证kotlin的幕后字段与后台返回数据一致即可.</li>
</ul>
</li>
<li><p>如果你项目中用的是<strong>fastjson</strong>，那么需要为每个字段添加<code>setter</code>/<code>getter</code> 方法。<strong>fastjson</strong>是根据<strong>Javabean</strong>属性来赋值的，所以必须保证属性名正确。由于之前<strong>fastjson</strong>出现过序列化成功，但是反序列化失败的bug，致使我放弃使用<strong>fastjson</strong>。虽然<strong>fastjson</strong>以快著称，但是快不是唯一的需求，它只在数据量大的时候有明显差异，数据量小的时候差异几乎可是忽略不计，所以我选择<strong>Gson</strong>。</p>
</li>
</ul>
<h3 id="细谈Java属性"><a href="#细谈Java属性" class="headerlink" title="细谈Java属性"></a>细谈Java属性</h3><p>在Java里面生成 <code>setter</code>/<code>getter</code> 方法是有一定规则的。</p>
<ul>
<li>如果类的成员变量的名字是xxx，那么为了更改或获取成员变量的值，即更改或获取属性，在类中可以使用两个方法：<ul>
<li>getXxx()，用来获取属性xxx。</li>
<li>setXxx()，用来修改属性xxx.。</li>
</ul>
</li>
<li>对于boolean类型的成员变量，即布尔逻辑类型的属性，允许使用”is”代替上面的”get”。</li>
<li>类中访问属性的方法都必须是public的，一般属性是private的。</li>
<li>类中如果有构造方法，那么这个构造方法也是public的并且是无参数的。</li>
</ul>
<p>因为Java属性是由 <code>setter</code>/<code>getter</code> 方法决定的，而不是字段。在一个Java类里面，如果有<code>void setName(String name)</code>和<code>String getName()</code>方法，那么可以认为这个类有<code>name</code>这个属性（<strong>可读可写</strong>），如果仅有get，那么这个<code>name</code>属性就是个<strong>只读属性</strong>。即使没有<code>name</code>这个字段也是一样成立的。</p>
<p>也就是说，对于 <code>boolean</code> 类型，<code>enabled</code> 和 <code>isEnabled</code> 这两个字段生成的<code>setter</code>/<code>getter</code> 方法是一致的。当这两个字段同时存在时，只能针对其中一个生成<code>setter</code>/<code>getter</code> 方法。看下图你就明白了</p>
<p><img src="/media/15057241768106.jpg" alt=""></p>
<p><code>isEnabled</code> 已经无法再生成了，因为已经存在了。在项目当中应该避免则这样的命名，选择其一即可。</p>

      

      
    </div>
    <div class="article-info article-info-index">
      
      
	<div class="article-tag tagcloud">
		<i class="icon-price-tags icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color3">Android</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color5">Java</a>
        		</li>
      		 
        		<li class="article-tag-list-item">
        			<a href="javascript:void(0)" class="js-tag article-tag-list-link color2">Kotlin</a>
        		</li>
      		
		</ul>
	</div>

      
	<div class="article-category tagcloud">
		<i class="icon-book icon"></i>
		<ul class="article-tag-list">
			 
        		<li class="article-tag-list-item">
        			<a href="/categories/Android//" class="article-tag-list-link color3">Android</a>
        		</li>
      		
		</ul>
	</div>


      
        <p class="article-more-link">
          <a class="article-more-a" href="/post/2d13bd4b.html">展开全文 >></a>
        </p>
      

      
      <div class="clearfix"></div>
    </div>
  </div>
</article>

<aside class="wrap-side-operation">
    <div class="mod-side-operation">
        
        <div class="jump-container" id="js-jump-container" style="display:none;">
            <a href="javascript:void(0)" class="mod-side-operation__jump-to-top">
                <i class="icon-font icon-back"></i>
            </a>
            <div id="js-jump-plan-container" class="jump-plan-container" style="top: -11px;">
                <i class="icon-font icon-plane jump-plane"></i>
            </div>
        </div>
        
        
    </div>
</aside>




  
  
    <nav id="page-nav">
      <span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><a class="extend next" rel="next" href="/page/2/">Next &raquo;</a>
    </nav>
  


          </div>
        </div>
      </div>
      <footer id="footer">
  <div class="outer">
    <div id="footer-info">
    	<div class="footer-left">
    		&copy; 2018 liyuanbiao
    	</div>
      	<div class="footer-right">
      		<a href="http://hexo.io/" target="_blank">Hexo</a>  Theme <a href="https://github.com/litten/hexo-theme-yilia" target="_blank">Yilia</a> by Litten
      	</div>
    </div>
  </div>
</footer>
    </div>
    <script>
	var yiliaConfig = {
		mathjax: false,
		isHome: true,
		isPost: false,
		isArchive: false,
		isTag: false,
		isCategory: false,
		open_in_new: false,
		toc_hide_index: true,
		root: "/",
		innerArchive: true,
		showTags: true
	}
</script>

<script>!function(t){function n(e){if(r[e])return r[e].exports;var i=r[e]={exports:{},id:e,loaded:!1};return t[e].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var r={};n.m=t,n.c=r,n.p="./",n(0)}([function(t,n,r){r(195),t.exports=r(191)},function(t,n,r){var e=r(3),i=r(52),o=r(27),u=r(28),c=r(53),f="prototype",a=function(t,n,r){var s,l,h,v,p=t&a.F,d=t&a.G,y=t&a.S,g=t&a.P,b=t&a.B,m=d?e:y?e[n]||(e[n]={}):(e[n]||{})[f],x=d?i:i[n]||(i[n]={}),w=x[f]||(x[f]={});d&&(r=n);for(s in r)l=!p&&m&&void 0!==m[s],h=(l?m:r)[s],v=b&&l?c(h,e):g&&"function"==typeof h?c(Function.call,h):h,m&&u(m,s,h,t&a.U),x[s]!=h&&o(x,s,v),g&&w[s]!=h&&(w[s]=h)};e.core=i,a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,n,r){var e=r(6);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){var r=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=r)},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n,r){var e=r(126)("wks"),i=r(76),o=r(3).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n,r){var e=r(94),i=r(33);t.exports=function(t){return e(i(t))}},function(t,n,r){t.exports=!r(4)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(2),i=r(167),o=r(50),u=Object.defineProperty;n.f=r(10)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){t.exports=!r(18)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(14),i=r(22);t.exports=r(12)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(20),i=r(58),o=r(42),u=Object.defineProperty;n.f=r(12)?Object.defineProperty:function(t,n,r){if(e(t),n=o(n,!0),e(r),i)try{return u(t,n,r)}catch(t){}if("get"in r||"set"in r)throw TypeError("Accessors not supported!");return"value"in r&&(t[n]=r.value),t}},function(t,n,r){var e=r(40)("wks"),i=r(23),o=r(5).Symbol,u="function"==typeof o;(t.exports=function(t){return e[t]||(e[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=e},function(t,n,r){var e=r(67),i=Math.min;t.exports=function(t){return t>0?i(e(t),9007199254740991):0}},function(t,n,r){var e=r(46);t.exports=function(t){return Object(e(t))}},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n,r){var e=r(63),i=r(34);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n,r){var e=r(21);t.exports=function(t){if(!e(t))throw TypeError(t+" is not an object!");return t}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n){var r={}.hasOwnProperty;t.exports=function(t,n){return r.call(t,n)}},function(t,n){var r=t.exports={version:"2.4.0"};"number"==typeof __e&&(__e=r)},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,r){var e=r(11),i=r(66);t.exports=r(10)?function(t,n,r){return e.f(t,n,i(1,r))}:function(t,n,r){return t[n]=r,t}},function(t,n,r){var e=r(3),i=r(27),o=r(24),u=r(76)("src"),c="toString",f=Function[c],a=(""+f).split(c);r(52).inspectSource=function(t){return f.call(t)},(t.exports=function(t,n,r,c){var f="function"==typeof r;f&&(o(r,"name")||i(r,"name",n)),t[n]!==r&&(f&&(o(r,u)||i(r,u,t[n]?""+t[n]:a.join(String(n)))),t===e?t[n]=r:c?t[n]?t[n]=r:i(t,n,r):(delete t[n],i(t,n,r)))})(Function.prototype,c,function(){return"function"==typeof this&&this[u]||f.call(this)})},function(t,n,r){var e=r(1),i=r(4),o=r(46),u=function(t,n,r,e){var i=String(o(t)),u="<"+n;return""!==r&&(u+=" "+r+'="'+String(e).replace(/"/g,"&quot;")+'"'),u+">"+i+"</"+n+">"};t.exports=function(t,n){var r={};r[t]=n(u),e(e.P+e.F*i(function(){var n=""[t]('"');return n!==n.toLowerCase()||n.split('"').length>3}),"String",r)}},function(t,n,r){var e=r(115),i=r(46);t.exports=function(t){return e(i(t))}},function(t,n,r){var e=r(116),i=r(66),o=r(30),u=r(50),c=r(24),f=r(167),a=Object.getOwnPropertyDescriptor;n.f=r(10)?a:function(t,n){if(t=o(t),n=u(n,!0),f)try{return a(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(24),i=r(17),o=r(145)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on  "+t);return t}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n){t.exports={}},function(t,n){t.exports=!0},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(14).f,i=r(8),o=r(15)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(40)("keys"),i=r(23);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n,r){var e=r(5),i="__core-js_shared__",o=e[i]||(e[i]={});t.exports=function(t){return o[t]||(o[t]={})}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},function(t,n,r){var e=r(21);t.exports=function(t,n){if(!e(t))return t;var r,i;if(n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;if("function"==typeof(r=t.valueOf)&&!e(i=r.call(t)))return i;if(!n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n,r){var e=r(5),i=r(25),o=r(36),u=r(44),c=r(14).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){n.f=r(15)},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on  "+t);return t}},function(t,n,r){var e=r(4);t.exports=function(t,n){return!!t&&e(function(){n?t.call(null,function(){},1):t.call(null)})}},function(t,n,r){var e=r(53),i=r(115),o=r(17),u=r(16),c=r(203);t.exports=function(t,n){var r=1==t,f=2==t,a=3==t,s=4==t,l=6==t,h=5==t||l,v=n||c;return function(n,c,p){for(var d,y,g=o(n),b=i(g),m=e(c,p,3),x=u(b.length),w=0,S=r?v(n,x):f?v(n,0):void 0;x>w;w++)if((h||w in b)&&(d=b[w],y=m(d,w,g),t))if(r)S[w]=y;else if(y)switch(t){case 3:return!0;case 5:return d;case 6:return w;case 2:S.push(d)}else if(s)return!1;return l?-1:a||s?s:S}}},function(t,n,r){var e=r(1),i=r(52),o=r(4);t.exports=function(t,n){var r=(i.Object||{})[t]||Object[t],u={};u[t]=n(r),e(e.S+e.F*o(function(){r(1)}),"Object",u)}},function(t,n,r){var e=r(6);t.exports=function(t,n){if(!e(t))return t;var r,i;if(n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;if("function"==typeof(r=t.valueOf)&&!e(i=r.call(t)))return i;if(!n&&"function"==typeof(r=t.toString)&&!e(i=r.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n,r){var e=r(5),i=r(25),o=r(91),u=r(13),c="prototype",f=function(t,n,r){var a,s,l,h=t&f.F,v=t&f.G,p=t&f.S,d=t&f.P,y=t&f.B,g=t&f.W,b=v?i:i[n]||(i[n]={}),m=b[c],x=v?e:p?e[n]:(e[n]||{})[c];v&&(r=n);for(a in r)(s=!h&&x&&void 0!==x[a])&&a in b||(l=s?x[a]:r[a],b[a]=v&&"function"!=typeof x[a]?r[a]:y&&s?o(l,e):g&&x[a]==l?function(t){var n=function(n,r,e){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(n);case 2:return new t(n,r)}return new t(n,r,e)}return t.apply(this,arguments)};return n[c]=t[c],n}(l):d&&"function"==typeof l?o(Function.call,l):l,d&&((b.virtual||(b.virtual={}))[a]=l,t&f.R&&m&&!m[a]&&u(m,a,l)))};f.F=1,f.G=2,f.S=4,f.P=8,f.B=16,f.W=32,f.U=64,f.R=128,t.exports=f},function(t,n){var r=t.exports={version:"2.4.0"};"number"==typeof __e&&(__e=r)},function(t,n,r){var e=r(26);t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,i){return t.call(n,r,e,i)}}return function(){return t.apply(n,arguments)}}},function(t,n,r){var e=r(183),i=r(1),o=r(126)("metadata"),u=o.store||(o.store=new(r(186))),c=function(t,n,r){var i=u.get(t);if(!i){if(!r)return;u.set(t,i=new e)}var o=i.get(n);if(!o){if(!r)return;i.set(n,o=new e)}return o},f=function(t,n,r){var e=c(n,r,!1);return void 0!==e&&e.has(t)},a=function(t,n,r){var e=c(n,r,!1);return void 0===e?void 0:e.get(t)},s=function(t,n,r,e){c(r,e,!0).set(t,n)},l=function(t,n){var r=c(t,n,!1),e=[];return r&&r.forEach(function(t,n){e.push(n)}),e},h=function(t){return void 0===t||"symbol"==typeof t?t:String(t)},v=function(t){i(i.S,"Reflect",t)};t.exports={store:u,map:c,has:f,get:a,set:s,keys:l,key:h,exp:v}},function(t,n,r){"use strict";if(r(10)){var e=r(69),i=r(3),o=r(4),u=r(1),c=r(127),f=r(152),a=r(53),s=r(68),l=r(66),h=r(27),v=r(73),p=r(67),d=r(16),y=r(75),g=r(50),b=r(24),m=r(180),x=r(114),w=r(6),S=r(17),_=r(137),O=r(70),E=r(32),P=r(71).f,j=r(154),F=r(76),M=r(7),A=r(48),N=r(117),T=r(146),I=r(155),k=r(80),L=r(123),R=r(74),C=r(130),D=r(160),U=r(11),W=r(31),G=U.f,B=W.f,V=i.RangeError,z=i.TypeError,q=i.Uint8Array,K="ArrayBuffer",J="Shared"+K,Y="BYTES_PER_ELEMENT",H="prototype",$=Array[H],X=f.ArrayBuffer,Q=f.DataView,Z=A(0),tt=A(2),nt=A(3),rt=A(4),et=A(5),it=A(6),ot=N(!0),ut=N(!1),ct=I.values,ft=I.keys,at=I.entries,st=$.lastIndexOf,lt=$.reduce,ht=$.reduceRight,vt=$.join,pt=$.sort,dt=$.slice,yt=$.toString,gt=$.toLocaleString,bt=M("iterator"),mt=M("toStringTag"),xt=F("typed_constructor"),wt=F("def_constructor"),St=c.CONSTR,_t=c.TYPED,Ot=c.VIEW,Et="Wrong length!",Pt=A(1,function(t,n){return Tt(T(t,t[wt]),n)}),jt=o(function(){return 1===new q(new Uint16Array([1]).buffer)[0]}),Ft=!!q&&!!q[H].set&&o(function(){new q(1).set({})}),Mt=function(t,n){if(void 0===t)throw z(Et);var r=+t,e=d(t);if(n&&!m(r,e))throw V(Et);return e},At=function(t,n){var r=p(t);if(r<0||r%n)throw V("Wrong offset!");return r},Nt=function(t){if(w(t)&&_t in t)return t;throw z(t+" is not a typed array!")},Tt=function(t,n){if(!(w(t)&&xt in t))throw z("It is not a typed array constructor!");return new t(n)},It=function(t,n){return kt(T(t,t[wt]),n)},kt=function(t,n){for(var r=0,e=n.length,i=Tt(t,e);e>r;)i[r]=n[r++];return i},Lt=function(t,n,r){G(t,n,{get:function(){return this._d[r]}})},Rt=function(t){var n,r,e,i,o,u,c=S(t),f=arguments.length,s=f>1?arguments[1]:void 0,l=void 0!==s,h=j(c);if(void 0!=h&&!_(h)){for(u=h.call(c),e=[],n=0;!(o=u.next()).done;n++)e.push(o.value);c=e}for(l&&f>2&&(s=a(s,arguments[2],2)),n=0,r=d(c.length),i=Tt(this,r);r>n;n++)i[n]=l?s(c[n],n):c[n];return i},Ct=function(){for(var t=0,n=arguments.length,r=Tt(this,n);n>t;)r[t]=arguments[t++];return r},Dt=!!q&&o(function(){gt.call(new q(1))}),Ut=function(){return gt.apply(Dt?dt.call(Nt(this)):Nt(this),arguments)},Wt={copyWithin:function(t,n){return D.call(Nt(this),t,n,arguments.length>2?arguments[2]:void 0)},every:function(t){return rt(Nt(this),t,arguments.length>1?arguments[1]:void 0)},fill:function(t){return C.apply(Nt(this),arguments)},filter:function(t){return It(this,tt(Nt(this),t,arguments.length>1?arguments[1]:void 0))},find:function(t){return et(Nt(this),t,arguments.length>1?arguments[1]:void 0)},findIndex:function(t){return it(Nt(this),t,arguments.length>1?arguments[1]:void 0)},forEach:function(t){Z(Nt(this),t,arguments.length>1?arguments[1]:void 0)},indexOf:function(t){return ut(Nt(this),t,arguments.length>1?arguments[1]:void 0)},includes:function(t){return ot(Nt(this),t,arguments.length>1?arguments[1]:void 0)},join:function(t){return vt.apply(Nt(this),arguments)},lastIndexOf:function(t){return st.apply(Nt(this),arguments)},map:function(t){return Pt(Nt(this),t,arguments.length>1?arguments[1]:void 0)},reduce:function(t){return lt.apply(Nt(this),arguments)},reduceRight:function(t){return ht.apply(Nt(this),arguments)},reverse:function(){for(var t,n=this,r=Nt(n).length,e=Math.floor(r/2),i=0;i<e;)t=n[i],n[i++]=n[--r],n[r]=t;return n},some:function(t){return nt(Nt(this),t,arguments.length>1?arguments[1]:void 0)},sort:function(t){return pt.call(Nt(this),t)},subarray:function(t,n){var r=Nt(this),e=r.length,i=y(t,e);return new(T(r,r[wt]))(r.buffer,r.byteOffset+i*r.BYTES_PER_ELEMENT,d((void 0===n?e:y(n,e))-i))}},Gt=function(t,n){return It(this,dt.call(Nt(this),t,n))},Bt=function(t){Nt(this);var n=At(arguments[1],1),r=this.length,e=S(t),i=d(e.length),o=0;if(i+n>r)throw V(Et);for(;o<i;)this[n+o]=e[o++]},Vt={entries:function(){return at.call(Nt(this))},keys:function(){return ft.call(Nt(this))},values:function(){return ct.call(Nt(this))}},zt=function(t,n){return w(t)&&t[_t]&&"symbol"!=typeof n&&n in t&&String(+n)==String(n)},qt=function(t,n){return zt(t,n=g(n,!0))?l(2,t[n]):B(t,n)},Kt=function(t,n,r){return!(zt(t,n=g(n,!0))&&w(r)&&b(r,"value"))||b(r,"get")||b(r,"set")||r.configurable||b(r,"writable")&&!r.writable||b(r,"enumerable")&&!r.enumerable?G(t,n,r):(t[n]=r.value,t)};St||(W.f=qt,U.f=Kt),u(u.S+u.F*!St,"Object",{getOwnPropertyDescriptor:qt,defineProperty:Kt}),o(function(){yt.call({})})&&(yt=gt=function(){return vt.call(this)});var Jt=v({},Wt);v(Jt,Vt),h(Jt,bt,Vt.values),v(Jt,{slice:Gt,set:Bt,constructor:function(){},toString:yt,toLocaleString:Ut}),Lt(Jt,"buffer","b"),Lt(Jt,"byteOffset","o"),Lt(Jt,"byteLength","l"),Lt(Jt,"length","e"),G(Jt,mt,{get:function(){return this[_t]}}),t.exports=function(t,n,r,f){f=!!f;var a=t+(f?"Clamped":"")+"Array",l="Uint8Array"!=a,v="get"+t,p="set"+t,y=i[a],g=y||{},b=y&&E(y),m=!y||!c.ABV,S={},_=y&&y[H],j=function(t,r){var e=t._d;return e.v[v](r*n+e.o,jt)},F=function(t,r,e){var i=t._d;f&&(e=(e=Math.round(e))<0?0:e>255?255:255&e),i.v[p](r*n+i.o,e,jt)},M=function(t,n){G(t,n,{get:function(){return j(this,n)},set:function(t){return F(this,n,t)},enumerable:!0})};m?(y=r(function(t,r,e,i){s(t,y,a,"_d");var o,u,c,f,l=0,v=0;if(w(r)){if(!(r instanceof X||(f=x(r))==K||f==J))return _t in r?kt(y,r):Rt.call(y,r);o=r,v=At(e,n);var p=r.byteLength;if(void 0===i){if(p%n)throw V(Et);if((u=p-v)<0)throw V(Et)}else if((u=d(i)*n)+v>p)throw V(Et);c=u/n}else c=Mt(r,!0),u=c*n,o=new X(u);for(h(t,"_d",{b:o,o:v,l:u,e:c,v:new Q(o)});l<c;)M(t,l++)}),_=y[H]=O(Jt),h(_,"constructor",y)):L(function(t){new y(null),new y(t)},!0)||(y=r(function(t,r,e,i){s(t,y,a);var o;return w(r)?r instanceof X||(o=x(r))==K||o==J?void 0!==i?new g(r,At(e,n),i):void 0!==e?new g(r,At(e,n)):new g(r):_t in r?kt(y,r):Rt.call(y,r):new g(Mt(r,l))}),Z(b!==Function.prototype?P(g).concat(P(b)):P(g),function(t){t in y||h(y,t,g[t])}),y[H]=_,e||(_.constructor=y));var A=_[bt],N=!!A&&("values"==A.name||void 0==A.name),T=Vt.values;h(y,xt,!0),h(_,_t,a),h(_,Ot,!0),h(_,wt,y),(f?new y(1)[mt]==a:mt in _)||G(_,mt,{get:function(){return a}}),S[a]=y,u(u.G+u.W+u.F*(y!=g),S),u(u.S,a,{BYTES_PER_ELEMENT:n,from:Rt,of:Ct}),Y in _||h(_,Y,n),u(u.P,a,Wt),R(a),u(u.P+u.F*Ft,a,{set:Bt}),u(u.P+u.F*!N,a,Vt),u(u.P+u.F*(_.toString!=yt),a,{toString:yt}),u(u.P+u.F*o(function(){new y(1).slice()}),a,{slice:Gt}),u(u.P+u.F*(o(function(){return[1,2].toLocaleString()!=new y([1,2]).toLocaleString()})||!o(function(){_.toLocaleString.call([1,2])})),a,{toLocaleString:Ut}),k[a]=N?A:T,e||N||h(_,bt,T)}}else t.exports=function(){}},function(t,n){var r={}.toString;t.exports=function(t){return r.call(t).slice(8,-1)}},function(t,n,r){var e=r(21),i=r(5).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,r){t.exports=!r(12)&&!r(18)(function(){return 7!=Object.defineProperty(r(57)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){"use strict";var e=r(36),i=r(51),o=r(64),u=r(13),c=r(8),f=r(35),a=r(96),s=r(38),l=r(103),h=r(15)("iterator"),v=!([].keys&&"next"in[].keys()),p="keys",d="values",y=function(){return this};t.exports=function(t,n,r,g,b,m,x){a(r,n,g);var w,S,_,O=function(t){if(!v&&t in F)return F[t];switch(t){case p:case d:return function(){return new r(this,t)}}return function(){return new r(this,t)}},E=n+" Iterator",P=b==d,j=!1,F=t.prototype,M=F[h]||F["@@iterator"]||b&&F[b],A=M||O(b),N=b?P?O("entries"):A:void 0,T="Array"==n?F.entries||M:M;if(T&&(_=l(T.call(new t)))!==Object.prototype&&(s(_,E,!0),e||c(_,h)||u(_,h,y)),P&&M&&M.name!==d&&(j=!0,A=function(){return M.call(this)}),e&&!x||!v&&!j&&F[h]||u(F,h,A),f[n]=A,f[E]=y,b)if(w={values:P?A:O(d),keys:m?A:O(p),entries:N},x)for(S in w)S in F||o(F,S,w[S]);else i(i.P+i.F*(v||j),n,w);return w}},function(t,n,r){var e=r(20),i=r(100),o=r(34),u=r(39)("IE_PROTO"),c=function(){},f="prototype",a=function(){var t,n=r(57)("iframe"),e=o.length;for(n.style.display="none",r(93).appendChild(n),n.src="javascript:",t=n.contentWindow.document,t.open(),t.write("<script>document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a[f][o[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c[f]=e(t),r=new c,c[f]=null,r[u]=t):r=a(),void 0===n?r:i(r,n)}},function(t,n,r){var e=r(63),i=r(34).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var e=r(8),i=r(9),o=r(90)(!1),u=r(39)("IE_PROTO");t.exports=function(t,n){var r,c=i(t),f=0,a=[];for(r in c)r!=u&&e(c,r)&&a.push(r);for(;n.length>f;)e(c,r=n[f++])&&(~o(a,r)||a.push(r));return a}},function(t,n,r){t.exports=r(13)},function(t,n,r){var e=r(76)("meta"),i=r(6),o=r(24),u=r(11).f,c=0,f=Object.isExtensible||function(){return!0},a=!r(4)(function(){return f(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!f(t))return"F";if(!n)return"E";s(t)}return t[e].i},h=function(t,n){if(!o(t,e)){if(!f(t))return!0;if(!n)return!1;s(t)}return t[e].w},v=function(t){return a&&p.NEED&&f(t)&&!o(t,e)&&s(t),t},p=t.exports={KEY:e,NEED:!1,fastKey:l,getWeak:h,onFreeze:v}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var r=Math.ceil,e=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?e:r)(t)}},function(t,n){t.exports=function(t,n,r,e){if(!(t instanceof n)||void 0!==e&&e in t)throw TypeError(r+": incorrect invocation!");return t}},function(t,n){t.exports=!1},function(t,n,r){var e=r(2),i=r(173),o=r(133),u=r(145)("IE_PROTO"),c=function(){},f="prototype",a=function(){var t,n=r(132)("iframe"),e=o.length;for(n.style.display="none",r(135).appendChild(n),n.src="javascript:",t=n.contentWindow.document,t.open(),t.write("<script>document.F=Object<\/script>"),t.close(),a=t.F;e--;)delete a[f][o[e]];return a()};t.exports=Object.create||function(t,n){var r;return null!==t?(c[f]=e(t),r=new c,c[f]=null,r[u]=t):r=a(),void 0===n?r:i(r,n)}},function(t,n,r){var e=r(175),i=r(133).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return e(t,i)}},function(t,n,r){var e=r(175),i=r(133);t.exports=Object.keys||function(t){return e(t,i)}},function(t,n,r){var e=r(28);t.exports=function(t,n,r){for(var i in n)e(t,i,n[i],r);return t}},function(t,n,r){"use strict";var e=r(3),i=r(11),o=r(10),u=r(7)("species");t.exports=function(t){var n=e[t];o&&n&&!n[u]&&i.f(n,u,{configurable:!0,get:function(){return this}})}},function(t,n,r){var e=r(67),i=Math.max,o=Math.min;t.exports=function(t,n){return t=e(t),t<0?i(t+n,0):o(t,n)}},function(t,n){var r=0,e=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++r+e).toString(36))}},function(t,n,r){var e=r(33);t.exports=function(t){return Object(e(t))}},function(t,n,r){var e=r(7)("unscopables"),i=Array.prototype;void 0==i[e]&&r(27)(i,e,{}),t.exports=function(t){i[e][t]=!0}},function(t,n,r){var e=r(53),i=r(169),o=r(137),u=r(2),c=r(16),f=r(154),a={},s={},n=t.exports=function(t,n,r,l,h){var v,p,d,y,g=h?function(){return t}:f(t),b=e(r,l,n?2:1),m=0;if("function"!=typeof g)throw TypeError(t+" is not iterable!");if(o(g)){for(v=c(t.length);v>m;m++)if((y=n?b(u(p=t[m])[0],p[1]):b(t[m]))===a||y===s)return y}else for(d=g.call(t);!(p=d.next()).done;)if((y=i(d,b,p.value,n))===a||y===s)return y};n.BREAK=a,n.RETURN=s},function(t,n){t.exports={}},function(t,n,r){var e=r(11).f,i=r(24),o=r(7)("toStringTag");t.exports=function(t,n,r){t&&!i(t=r?t:t.prototype,o)&&e(t,o,{configurable:!0,value:n})}},function(t,n,r){var e=r(1),i=r(46),o=r(4),u=r(150),c="["+u+"]",f="​",a=RegExp("^"+c+c+"*"),s=RegExp(c+c+"*$"),l=function(t,n,r){var i={},c=o(function(){return!!u[t]()||f[t]()!=f}),a=i[t]=c?n(h):u[t];r&&(i[r]=a),e(e.P+e.F*c,"String",i)},h=l.trim=function(t,n){return t=String(i(t)),1&n&&(t=t.replace(a,"")),2&n&&(t=t.replace(s,"")),t};t.exports=l},function(t,n,r){t.exports={default:r(86),__esModule:!0}},function(t,n,r){t.exports={default:r(87),__esModule:!0}},function(t,n,r){"use strict";function e(t){return t&&t.__esModule?t:{default:t}}n.__esModule=!0;var i=r(84),o=e(i),u=r(83),c=e(u),f="function"==typeof c.default&&"symbol"==typeof o.default?function(t){return typeof t}:function(t){return t&&"function"==typeof c.default&&t.constructor===c.default&&t!==c.default.prototype?"symbol":typeof t};n.default="function"==typeof c.default&&"symbol"===f(o.default)?function(t){return void 0===t?"undefined":f(t)}:function(t){return t&&"function"==typeof c.default&&t.constructor===c.default&&t!==c.default.prototype?"symbol":void 0===t?"undefined":f(t)}},function(t,n,r){r(110),r(108),r(111),r(112),t.exports=r(25).Symbol},function(t,n,r){r(109),r(113),t.exports=r(44).f("iterator")},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n){t.exports=function(){}},function(t,n,r){var e=r(9),i=r(106),o=r(105);t.exports=function(t){return function(n,r,u){var c,f=e(n),a=i(f.length),s=o(u,a);if(t&&r!=r){for(;a>s;)if((c=f[s++])!=c)return!0}else for(;a>s;s++)if((t||s in f)&&f[s]===r)return t||s||0;return!t&&-1}}},function(t,n,r){var e=r(88);t.exports=function(t,n,r){if(e(t),void 0===n)return t;switch(r){case 1:return function(r){return t.call(n,r)};case 2:return function(r,e){return t.call(n,r,e)};case 3:return function(r,e,i){return t.call(n,r,e,i)}}return function(){return t.apply(n,arguments)}}},function(t,n,r){var e=r(19),i=r(62),o=r(37);t.exports=function(t){var n=e(t),r=i.f;if(r)for(var u,c=r(t),f=o.f,a=0;c.length>a;)f.call(t,u=c[a++])&&n.push(u);return n}},function(t,n,r){t.exports=r(5).document&&document.documentElement},function(t,n,r){var e=r(56);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n,r){var e=r(56);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){"use strict";var e=r(60),i=r(22),o=r(38),u={};r(13)(u,r(15)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n,r){var e=r(19),i=r(9);t.exports=function(t,n){for(var r,o=i(t),u=e(o),c=u.length,f=0;c>f;)if(o[r=u[f++]]===n)return r}},function(t,n,r){var e=r(23)("meta"),i=r(21),o=r(8),u=r(14).f,c=0,f=Object.isExtensible||function(){return!0},a=!r(18)(function(){return f(Object.preventExtensions({}))}),s=function(t){u(t,e,{value:{i:"O"+ ++c,w:{}}})},l=function(t,n){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,e)){if(!f(t))return"F";if(!n)return"E";s(t)}return t[e].i},h=function(t,n){if(!o(t,e)){if(!f(t))return!0;if(!n)return!1;s(t)}return t[e].w},v=function(t){return a&&p.NEED&&f(t)&&!o(t,e)&&s(t),t},p=t.exports={KEY:e,NEED:!1,fastKey:l,getWeak:h,onFreeze:v}},function(t,n,r){var e=r(14),i=r(20),o=r(19);t.exports=r(12)?Object.defineProperties:function(t,n){i(t);for(var r,u=o(n),c=u.length,f=0;c>f;)e.f(t,r=u[f++],n[r]);return t}},function(t,n,r){var e=r(37),i=r(22),o=r(9),u=r(42),c=r(8),f=r(58),a=Object.getOwnPropertyDescriptor;n.f=r(12)?a:function(t,n){if(t=o(t),n=u(n,!0),f)try{return a(t,n)}catch(t){}if(c(t,n))return i(!e.f.call(t,n),t[n])}},function(t,n,r){var e=r(9),i=r(61).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],c=function(t){try{return i(t)}catch(t){return u.slice()}};t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?c(t):i(e(t))}},function(t,n,r){var e=r(8),i=r(77),o=r(39)("IE_PROTO"),u=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),e(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?u:null}},function(t,n,r){var e=r(41),i=r(33);t.exports=function(t){return function(n,r){var o,u,c=String(i(n)),f=e(r),a=c.length;return f<0||f>=a?t?"":void 0:(o=c.charCodeAt(f),o<55296||o>56319||f+1===a||(u=c.charCodeAt(f+1))<56320||u>57343?t?c.charAt(f):o:t?c.slice(f,f+2):u-56320+(o-55296<<10)+65536)}}},function(t,n,r){var e=r(41),i=Math.max,o=Math.min;t.exports=function(t,n){return t=e(t),t<0?i(t+n,0):o(t,n)}},function(t,n,r){var e=r(41),i=Math.min;t.exports=function(t){return t>0?i(e(t),9007199254740991):0}},function(t,n,r){"use strict";var e=r(89),i=r(97),o=r(35),u=r(9);t.exports=r(59)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):"keys"==n?i(0,r):"values"==n?i(0,t[r]):i(0,[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n){},function(t,n,r){"use strict";var e=r(104)(!0);r(59)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";var e=r(5),i=r(8),o=r(12),u=r(51),c=r(64),f=r(99).KEY,a=r(18),s=r(40),l=r(38),h=r(23),v=r(15),p=r(44),d=r(43),y=r(98),g=r(92),b=r(95),m=r(20),x=r(9),w=r(42),S=r(22),_=r(60),O=r(102),E=r(101),P=r(14),j=r(19),F=E.f,M=P.f,A=O.f,N=e.Symbol,T=e.JSON,I=T&&T.stringify,k="prototype",L=v("_hidden"),R=v("toPrimitive"),C={}.propertyIsEnumerable,D=s("symbol-registry"),U=s("symbols"),W=s("op-symbols"),G=Object[k],B="function"==typeof N,V=e.QObject,z=!V||!V[k]||!V[k].findChild,q=o&&a(function(){return 7!=_(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=F(G,n);e&&delete G[n],M(t,n,r),e&&t!==G&&M(G,n,e)}:M,K=function(t){var n=U[t]=_(N[k]);return n._k=t,n},J=B&&"symbol"==typeof N.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof N},Y=function(t,n,r){return t===G&&Y(W,n,r),m(t),n=w(n,!0),m(r),i(U,n)?(r.enumerable?(i(t,L)&&t[L][n]&&(t[L][n]=!1),r=_(r,{enumerable:S(0,!1)})):(i(t,L)||M(t,L,S(1,{})),t[L][n]=!0),q(t,n,r)):M(t,n,r)},H=function(t,n){m(t);for(var r,e=g(n=x(n)),i=0,o=e.length;o>i;)Y(t,r=e[i++],n[r]);return t},$=function(t,n){return void 0===n?_(t):H(_(t),n)},X=function(t){var n=C.call(this,t=w(t,!0));return!(this===G&&i(U,t)&&!i(W,t))&&(!(n||!i(this,t)||!i(U,t)||i(this,L)&&this[L][t])||n)},Q=function(t,n){if(t=x(t),n=w(n,!0),t!==G||!i(U,n)||i(W,n)){var r=F(t,n);return!r||!i(U,n)||i(t,L)&&t[L][n]||(r.enumerable=!0),r}},Z=function(t){for(var n,r=A(x(t)),e=[],o=0;r.length>o;)i(U,n=r[o++])||n==L||n==f||e.push(n);return e},tt=function(t){for(var n,r=t===G,e=A(r?W:x(t)),o=[],u=0;e.length>u;)!i(U,n=e[u++])||r&&!i(G,n)||o.push(U[n]);return o};B||(N=function(){if(this instanceof N)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===G&&n.call(W,r),i(this,L)&&i(this[L],t)&&(this[L][t]=!1),q(this,t,S(1,r))};return o&&z&&q(G,t,{configurable:!0,set:n}),K(t)},c(N[k],"toString",function(){return this._k}),E.f=Q,P.f=Y,r(61).f=O.f=Z,r(37).f=X,r(62).f=tt,o&&!r(36)&&c(G,"propertyIsEnumerable",X,!0),p.f=function(t){return K(v(t))}),u(u.G+u.W+u.F*!B,{Symbol:N});for(var nt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),rt=0;nt.length>rt;)v(nt[rt++]);for(var nt=j(v.store),rt=0;nt.length>rt;)d(nt[rt++]);u(u.S+u.F*!B,"Symbol",{for:function(t){return i(D,t+="")?D[t]:D[t]=N(t)},keyFor:function(t){if(J(t))return y(D,t);throw TypeError(t+" is not a symbol!")},useSetter:function(){z=!0},useSimple:function(){z=!1}}),u(u.S+u.F*!B,"Object",{create:$,defineProperty:Y,defineProperties:H,getOwnPropertyDescriptor:Q,getOwnPropertyNames:Z,getOwnPropertySymbols:tt}),T&&u(u.S+u.F*(!B||a(function(){var t=N();return"[null]"!=I([t])||"{}"!=I({a:t})||"{}"!=I(Object(t))})),"JSON",{stringify:function(t){if(void 0!==t&&!J(t)){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);return n=e[1],"function"==typeof n&&(r=n),!r&&b(n)||(n=function(t,n){if(r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,I.apply(T,e)}}}),N[k][R]||r(13)(N[k],R,N[k].valueOf),l(N,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){r(43)("asyncIterator")},function(t,n,r){r(43)("observable")},function(t,n,r){r(107);for(var e=r(5),i=r(13),o=r(35),u=r(15)("toStringTag"),c=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],f=0;f<5;f++){var a=c[f],s=e[a],l=s&&s.prototype;l&&!l[u]&&i(l,u,a),o[a]=o.Array}},function(t,n,r){var e=r(45),i=r(7)("toStringTag"),o="Arguments"==e(function(){return arguments}()),u=function(t,n){try{return t[n]}catch(t){}};t.exports=function(t){var n,r,c;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(r=u(n=Object(t),i))?r:o?e(n):"Object"==(c=e(n))&&"function"==typeof n.callee?"Arguments":c}},function(t,n,r){var e=r(45);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==e(t)?t.split(""):Object(t)}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,r){var e=r(30),i=r(16),o=r(75);t.exports=function(t){return function(n,r,u){var c,f=e(n),a=i(f.length),s=o(u,a);if(t&&r!=r){for(;a>s;)if((c=f[s++])!=c)return!0}else for(;a>s;s++)if((t||s in f)&&f[s]===r)return t||s||0;return!t&&-1}}},function(t,n,r){"use strict";var e=r(3),i=r(1),o=r(28),u=r(73),c=r(65),f=r(79),a=r(68),s=r(6),l=r(4),h=r(123),v=r(81),p=r(136);t.exports=function(t,n,r,d,y,g){var b=e[t],m=b,x=y?"set":"add",w=m&&m.prototype,S={},_=function(t){var n=w[t];o(w,t,"delete"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"has"==t?function(t){return!(g&&!s(t))&&n.call(this,0===t?0:t)}:"get"==t?function(t){return g&&!s(t)?void 0:n.call(this,0===t?0:t)}:"add"==t?function(t){return n.call(this,0===t?0:t),this}:function(t,r){return n.call(this,0===t?0:t,r),this})};if("function"==typeof m&&(g||w.forEach&&!l(function(){(new m).entries().next()}))){var O=new m,E=O[x](g?{}:-0,1)!=O,P=l(function(){O.has(1)}),j=h(function(t){new m(t)}),F=!g&&l(function(){for(var t=new m,n=5;n--;)t[x](n,n);return!t.has(-0)});j||(m=n(function(n,r){a(n,m,t);var e=p(new b,n,m);return void 0!=r&&f(r,y,e[x],e),e}),m.prototype=w,w.constructor=m),(P||F)&&(_("delete"),_("has"),y&&_("get")),(F||E)&&_(x),g&&w.clear&&delete w.clear}else m=d.getConstructor(n,t,y,x),u(m.prototype,r),c.NEED=!0;return v(m,t),S[t]=m,i(i.G+i.W+i.F*(m!=b),S),g||d.setStrong(m,t,y),m}},function(t,n,r){"use strict";var e=r(27),i=r(28),o=r(4),u=r(46),c=r(7);t.exports=function(t,n,r){var f=c(t),a=r(u,f,""[t]),s=a[0],l=a[1];o(function(){var n={};return n[f]=function(){return 7},7!=""[t](n)})&&(i(String.prototype,t,s),e(RegExp.prototype,f,2==n?function(t,n){return l.call(t,this,n)}:function(t){return l.call(t,this)}))}
},function(t,n,r){"use strict";var e=r(2);t.exports=function(){var t=e(this),n="";return t.global&&(n+="g"),t.ignoreCase&&(n+="i"),t.multiline&&(n+="m"),t.unicode&&(n+="u"),t.sticky&&(n+="y"),n}},function(t,n){t.exports=function(t,n,r){var e=void 0===r;switch(n.length){case 0:return e?t():t.call(r);case 1:return e?t(n[0]):t.call(r,n[0]);case 2:return e?t(n[0],n[1]):t.call(r,n[0],n[1]);case 3:return e?t(n[0],n[1],n[2]):t.call(r,n[0],n[1],n[2]);case 4:return e?t(n[0],n[1],n[2],n[3]):t.call(r,n[0],n[1],n[2],n[3])}return t.apply(r,n)}},function(t,n,r){var e=r(6),i=r(45),o=r(7)("match");t.exports=function(t){var n;return e(t)&&(void 0!==(n=t[o])?!!n:"RegExp"==i(t))}},function(t,n,r){var e=r(7)("iterator"),i=!1;try{var o=[7][e]();o.return=function(){i=!0},Array.from(o,function(){throw 2})}catch(t){}t.exports=function(t,n){if(!n&&!i)return!1;var r=!1;try{var o=[7],u=o[e]();u.next=function(){return{done:r=!0}},o[e]=function(){return u},t(o)}catch(t){}return r}},function(t,n,r){t.exports=r(69)||!r(4)(function(){var t=Math.random();__defineSetter__.call(null,t,function(){}),delete r(3)[t]})},function(t,n){n.f=Object.getOwnPropertySymbols},function(t,n,r){var e=r(3),i="__core-js_shared__",o=e[i]||(e[i]={});t.exports=function(t){return o[t]||(o[t]={})}},function(t,n,r){for(var e,i=r(3),o=r(27),u=r(76),c=u("typed_array"),f=u("view"),a=!(!i.ArrayBuffer||!i.DataView),s=a,l=0,h="Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array".split(",");l<9;)(e=i[h[l++]])?(o(e.prototype,c,!0),o(e.prototype,f,!0)):s=!1;t.exports={ABV:a,CONSTR:s,TYPED:c,VIEW:f}},function(t,n){"use strict";var r={versions:function(){var t=window.navigator.userAgent;return{trident:t.indexOf("Trident")>-1,presto:t.indexOf("Presto")>-1,webKit:t.indexOf("AppleWebKit")>-1,gecko:t.indexOf("Gecko")>-1&&-1==t.indexOf("KHTML"),mobile:!!t.match(/AppleWebKit.*Mobile.*/),ios:!!t.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),android:t.indexOf("Android")>-1||t.indexOf("Linux")>-1,iPhone:t.indexOf("iPhone")>-1||t.indexOf("Mac")>-1,iPad:t.indexOf("iPad")>-1,webApp:-1==t.indexOf("Safari"),weixin:-1==t.indexOf("MicroMessenger")}}()};t.exports=r},function(t,n,r){"use strict";var e=r(85),i=function(t){return t&&t.__esModule?t:{default:t}}(e),o=function(){function t(t,n,e){return n||e?String.fromCharCode(n||e):r[t]||t}function n(t){return e[t]}var r={"&quot;":'"',"&lt;":"<","&gt;":">","&amp;":"&","&nbsp;":" "},e={};for(var u in r)e[r[u]]=u;return r["&apos;"]="'",e["'"]="&#39;",{encode:function(t){return t?(""+t).replace(/['<> "&]/g,n).replace(/\r?\n/g,"<br/>").replace(/\s/g,"&nbsp;"):""},decode:function(n){return n?(""+n).replace(/<br\s*\/?>/gi,"\n").replace(/&quot;|&lt;|&gt;|&amp;|&nbsp;|&apos;|&#(\d+);|&#(\d+)/g,t).replace(/\u00a0/g," "):""},encodeBase16:function(t){if(!t)return t;t+="";for(var n=[],r=0,e=t.length;e>r;r++)n.push(t.charCodeAt(r).toString(16).toUpperCase());return n.join("")},encodeBase16forJSON:function(t){if(!t)return t;t=t.replace(/[\u4E00-\u9FBF]/gi,function(t){return escape(t).replace("%u","\\u")});for(var n=[],r=0,e=t.length;e>r;r++)n.push(t.charCodeAt(r).toString(16).toUpperCase());return n.join("")},decodeBase16:function(t){if(!t)return t;t+="";for(var n=[],r=0,e=t.length;e>r;r+=2)n.push(String.fromCharCode("0x"+t.slice(r,r+2)));return n.join("")},encodeObject:function(t){if(t instanceof Array)for(var n=0,r=t.length;r>n;n++)t[n]=o.encodeObject(t[n]);else if("object"==(void 0===t?"undefined":(0,i.default)(t)))for(var e in t)t[e]=o.encodeObject(t[e]);else if("string"==typeof t)return o.encode(t);return t},loadScript:function(t){var n=document.createElement("script");document.getElementsByTagName("body")[0].appendChild(n),n.setAttribute("src",t)},addLoadEvent:function(t){var n=window.onload;"function"!=typeof window.onload?window.onload=t:window.onload=function(){n(),t()}}}}();t.exports=o},function(t,n,r){"use strict";var e=r(17),i=r(75),o=r(16);t.exports=function(t){for(var n=e(this),r=o(n.length),u=arguments.length,c=i(u>1?arguments[1]:void 0,r),f=u>2?arguments[2]:void 0,a=void 0===f?r:i(f,r);a>c;)n[c++]=t;return n}},function(t,n,r){"use strict";var e=r(11),i=r(66);t.exports=function(t,n,r){n in t?e.f(t,n,i(0,r)):t[n]=r}},function(t,n,r){var e=r(6),i=r(3).document,o=e(i)&&e(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,r){var e=r(7)("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(r){try{return n[e]=!1,!"/./"[t](n)}catch(t){}}return!0}},function(t,n,r){t.exports=r(3).document&&document.documentElement},function(t,n,r){var e=r(6),i=r(144).set;t.exports=function(t,n,r){var o,u=n.constructor;return u!==r&&"function"==typeof u&&(o=u.prototype)!==r.prototype&&e(o)&&i&&i(t,o),t}},function(t,n,r){var e=r(80),i=r(7)("iterator"),o=Array.prototype;t.exports=function(t){return void 0!==t&&(e.Array===t||o[i]===t)}},function(t,n,r){var e=r(45);t.exports=Array.isArray||function(t){return"Array"==e(t)}},function(t,n,r){"use strict";var e=r(70),i=r(66),o=r(81),u={};r(27)(u,r(7)("iterator"),function(){return this}),t.exports=function(t,n,r){t.prototype=e(u,{next:i(1,r)}),o(t,n+" Iterator")}},function(t,n,r){"use strict";var e=r(69),i=r(1),o=r(28),u=r(27),c=r(24),f=r(80),a=r(139),s=r(81),l=r(32),h=r(7)("iterator"),v=!([].keys&&"next"in[].keys()),p="keys",d="values",y=function(){return this};t.exports=function(t,n,r,g,b,m,x){a(r,n,g);var w,S,_,O=function(t){if(!v&&t in F)return F[t];switch(t){case p:case d:return function(){return new r(this,t)}}return function(){return new r(this,t)}},E=n+" Iterator",P=b==d,j=!1,F=t.prototype,M=F[h]||F["@@iterator"]||b&&F[b],A=M||O(b),N=b?P?O("entries"):A:void 0,T="Array"==n?F.entries||M:M;if(T&&(_=l(T.call(new t)))!==Object.prototype&&(s(_,E,!0),e||c(_,h)||u(_,h,y)),P&&M&&M.name!==d&&(j=!0,A=function(){return M.call(this)}),e&&!x||!v&&!j&&F[h]||u(F,h,A),f[n]=A,f[E]=y,b)if(w={values:P?A:O(d),keys:m?A:O(p),entries:N},x)for(S in w)S in F||o(F,S,w[S]);else i(i.P+i.F*(v||j),n,w);return w}},function(t,n){var r=Math.expm1;t.exports=!r||r(10)>22025.465794806718||r(10)<22025.465794806718||-2e-17!=r(-2e-17)?function(t){return 0==(t=+t)?t:t>-1e-6&&t<1e-6?t+t*t/2:Math.exp(t)-1}:r},function(t,n){t.exports=Math.sign||function(t){return 0==(t=+t)||t!=t?t:t<0?-1:1}},function(t,n,r){var e=r(3),i=r(151).set,o=e.MutationObserver||e.WebKitMutationObserver,u=e.process,c=e.Promise,f="process"==r(45)(u);t.exports=function(){var t,n,r,a=function(){var e,i;for(f&&(e=u.domain)&&e.exit();t;){i=t.fn,t=t.next;try{i()}catch(e){throw t?r():n=void 0,e}}n=void 0,e&&e.enter()};if(f)r=function(){u.nextTick(a)};else if(o){var s=!0,l=document.createTextNode("");new o(a).observe(l,{characterData:!0}),r=function(){l.data=s=!s}}else if(c&&c.resolve){var h=c.resolve();r=function(){h.then(a)}}else r=function(){i.call(e,a)};return function(e){var i={fn:e,next:void 0};n&&(n.next=i),t||(t=i,r()),n=i}}},function(t,n,r){var e=r(6),i=r(2),o=function(t,n){if(i(t),!e(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,e){try{e=r(53)(Function.call,r(31).f(Object.prototype,"__proto__").set,2),e(t,[]),n=!(t instanceof Array)}catch(t){n=!0}return function(t,r){return o(t,r),n?t.__proto__=r:e(t,r),t}}({},!1):void 0),check:o}},function(t,n,r){var e=r(126)("keys"),i=r(76);t.exports=function(t){return e[t]||(e[t]=i(t))}},function(t,n,r){var e=r(2),i=r(26),o=r(7)("species");t.exports=function(t,n){var r,u=e(t).constructor;return void 0===u||void 0==(r=e(u)[o])?n:i(r)}},function(t,n,r){var e=r(67),i=r(46);t.exports=function(t){return function(n,r){var o,u,c=String(i(n)),f=e(r),a=c.length;return f<0||f>=a?t?"":void 0:(o=c.charCodeAt(f),o<55296||o>56319||f+1===a||(u=c.charCodeAt(f+1))<56320||u>57343?t?c.charAt(f):o:t?c.slice(f,f+2):u-56320+(o-55296<<10)+65536)}}},function(t,n,r){var e=r(122),i=r(46);t.exports=function(t,n,r){if(e(n))throw TypeError("String#"+r+" doesn't accept regex!");return String(i(t))}},function(t,n,r){"use strict";var e=r(67),i=r(46);t.exports=function(t){var n=String(i(this)),r="",o=e(t);if(o<0||o==1/0)throw RangeError("Count can't be negative");for(;o>0;(o>>>=1)&&(n+=n))1&o&&(r+=n);return r}},function(t,n){t.exports="\t\n\v\f\r   ᠎             　\u2028\u2029\ufeff"},function(t,n,r){var e,i,o,u=r(53),c=r(121),f=r(135),a=r(132),s=r(3),l=s.process,h=s.setImmediate,v=s.clearImmediate,p=s.MessageChannel,d=0,y={},g="onreadystatechange",b=function(){var t=+this;if(y.hasOwnProperty(t)){var n=y[t];delete y[t],n()}},m=function(t){b.call(t.data)};h&&v||(h=function(t){for(var n=[],r=1;arguments.length>r;)n.push(arguments[r++]);return y[++d]=function(){c("function"==typeof t?t:Function(t),n)},e(d),d},v=function(t){delete y[t]},"process"==r(45)(l)?e=function(t){l.nextTick(u(b,t,1))}:p?(i=new p,o=i.port2,i.port1.onmessage=m,e=u(o.postMessage,o,1)):s.addEventListener&&"function"==typeof postMessage&&!s.importScripts?(e=function(t){s.postMessage(t+"","*")},s.addEventListener("message",m,!1)):e=g in a("script")?function(t){f.appendChild(a("script"))[g]=function(){f.removeChild(this),b.call(t)}}:function(t){setTimeout(u(b,t,1),0)}),t.exports={set:h,clear:v}},function(t,n,r){"use strict";var e=r(3),i=r(10),o=r(69),u=r(127),c=r(27),f=r(73),a=r(4),s=r(68),l=r(67),h=r(16),v=r(71).f,p=r(11).f,d=r(130),y=r(81),g="ArrayBuffer",b="DataView",m="prototype",x="Wrong length!",w="Wrong index!",S=e[g],_=e[b],O=e.Math,E=e.RangeError,P=e.Infinity,j=S,F=O.abs,M=O.pow,A=O.floor,N=O.log,T=O.LN2,I="buffer",k="byteLength",L="byteOffset",R=i?"_b":I,C=i?"_l":k,D=i?"_o":L,U=function(t,n,r){var e,i,o,u=Array(r),c=8*r-n-1,f=(1<<c)-1,a=f>>1,s=23===n?M(2,-24)-M(2,-77):0,l=0,h=t<0||0===t&&1/t<0?1:0;for(t=F(t),t!=t||t===P?(i=t!=t?1:0,e=f):(e=A(N(t)/T),t*(o=M(2,-e))<1&&(e--,o*=2),t+=e+a>=1?s/o:s*M(2,1-a),t*o>=2&&(e++,o/=2),e+a>=f?(i=0,e=f):e+a>=1?(i=(t*o-1)*M(2,n),e+=a):(i=t*M(2,a-1)*M(2,n),e=0));n>=8;u[l++]=255&i,i/=256,n-=8);for(e=e<<n|i,c+=n;c>0;u[l++]=255&e,e/=256,c-=8);return u[--l]|=128*h,u},W=function(t,n,r){var e,i=8*r-n-1,o=(1<<i)-1,u=o>>1,c=i-7,f=r-1,a=t[f--],s=127&a;for(a>>=7;c>0;s=256*s+t[f],f--,c-=8);for(e=s&(1<<-c)-1,s>>=-c,c+=n;c>0;e=256*e+t[f],f--,c-=8);if(0===s)s=1-u;else{if(s===o)return e?NaN:a?-P:P;e+=M(2,n),s-=u}return(a?-1:1)*e*M(2,s-n)},G=function(t){return t[3]<<24|t[2]<<16|t[1]<<8|t[0]},B=function(t){return[255&t]},V=function(t){return[255&t,t>>8&255]},z=function(t){return[255&t,t>>8&255,t>>16&255,t>>24&255]},q=function(t){return U(t,52,8)},K=function(t){return U(t,23,4)},J=function(t,n,r){p(t[m],n,{get:function(){return this[r]}})},Y=function(t,n,r,e){var i=+r,o=l(i);if(i!=o||o<0||o+n>t[C])throw E(w);var u=t[R]._b,c=o+t[D],f=u.slice(c,c+n);return e?f:f.reverse()},H=function(t,n,r,e,i,o){var u=+r,c=l(u);if(u!=c||c<0||c+n>t[C])throw E(w);for(var f=t[R]._b,a=c+t[D],s=e(+i),h=0;h<n;h++)f[a+h]=s[o?h:n-h-1]},$=function(t,n){s(t,S,g);var r=+n,e=h(r);if(r!=e)throw E(x);return e};if(u.ABV){if(!a(function(){new S})||!a(function(){new S(.5)})){S=function(t){return new j($(this,t))};for(var X,Q=S[m]=j[m],Z=v(j),tt=0;Z.length>tt;)(X=Z[tt++])in S||c(S,X,j[X]);o||(Q.constructor=S)}var nt=new _(new S(2)),rt=_[m].setInt8;nt.setInt8(0,2147483648),nt.setInt8(1,2147483649),!nt.getInt8(0)&&nt.getInt8(1)||f(_[m],{setInt8:function(t,n){rt.call(this,t,n<<24>>24)},setUint8:function(t,n){rt.call(this,t,n<<24>>24)}},!0)}else S=function(t){var n=$(this,t);this._b=d.call(Array(n),0),this[C]=n},_=function(t,n,r){s(this,_,b),s(t,S,b);var e=t[C],i=l(n);if(i<0||i>e)throw E("Wrong offset!");if(r=void 0===r?e-i:h(r),i+r>e)throw E(x);this[R]=t,this[D]=i,this[C]=r},i&&(J(S,k,"_l"),J(_,I,"_b"),J(_,k,"_l"),J(_,L,"_o")),f(_[m],{getInt8:function(t){return Y(this,1,t)[0]<<24>>24},getUint8:function(t){return Y(this,1,t)[0]},getInt16:function(t){var n=Y(this,2,t,arguments[1]);return(n[1]<<8|n[0])<<16>>16},getUint16:function(t){var n=Y(this,2,t,arguments[1]);return n[1]<<8|n[0]},getInt32:function(t){return G(Y(this,4,t,arguments[1]))},getUint32:function(t){return G(Y(this,4,t,arguments[1]))>>>0},getFloat32:function(t){return W(Y(this,4,t,arguments[1]),23,4)},getFloat64:function(t){return W(Y(this,8,t,arguments[1]),52,8)},setInt8:function(t,n){H(this,1,t,B,n)},setUint8:function(t,n){H(this,1,t,B,n)},setInt16:function(t,n){H(this,2,t,V,n,arguments[2])},setUint16:function(t,n){H(this,2,t,V,n,arguments[2])},setInt32:function(t,n){H(this,4,t,z,n,arguments[2])},setUint32:function(t,n){H(this,4,t,z,n,arguments[2])},setFloat32:function(t,n){H(this,4,t,K,n,arguments[2])},setFloat64:function(t,n){H(this,8,t,q,n,arguments[2])}});y(S,g),y(_,b),c(_[m],u.VIEW,!0),n[g]=S,n[b]=_},function(t,n,r){var e=r(3),i=r(52),o=r(69),u=r(182),c=r(11).f;t.exports=function(t){var n=i.Symbol||(i.Symbol=o?{}:e.Symbol||{});"_"==t.charAt(0)||t in n||c(n,t,{value:u.f(t)})}},function(t,n,r){var e=r(114),i=r(7)("iterator"),o=r(80);t.exports=r(52).getIteratorMethod=function(t){if(void 0!=t)return t[i]||t["@@iterator"]||o[e(t)]}},function(t,n,r){"use strict";var e=r(78),i=r(170),o=r(80),u=r(30);t.exports=r(140)(Array,"Array",function(t,n){this._t=u(t),this._i=0,this._k=n},function(){var t=this._t,n=this._k,r=this._i++;return!t||r>=t.length?(this._t=void 0,i(1)):"keys"==n?i(0,r):"values"==n?i(0,t[r]):i(0,[r,t[r]])},"values"),o.Arguments=o.Array,e("keys"),e("values"),e("entries")},function(t,n){function r(t,n){t.classList?t.classList.add(n):t.className+=" "+n}t.exports=r},function(t,n){function r(t,n){if(t.classList)t.classList.remove(n);else{var r=new RegExp("(^|\\b)"+n.split(" ").join("|")+"(\\b|$)","gi");t.className=t.className.replace(r," ")}}t.exports=r},function(t,n){function r(){throw new Error("setTimeout has not been defined")}function e(){throw new Error("clearTimeout has not been defined")}function i(t){if(s===setTimeout)return setTimeout(t,0);if((s===r||!s)&&setTimeout)return s=setTimeout,setTimeout(t,0);try{return s(t,0)}catch(n){try{return s.call(null,t,0)}catch(n){return s.call(this,t,0)}}}function o(t){if(l===clearTimeout)return clearTimeout(t);if((l===e||!l)&&clearTimeout)return l=clearTimeout,clearTimeout(t);try{return l(t)}catch(n){try{return l.call(null,t)}catch(n){return l.call(this,t)}}}function u(){d&&v&&(d=!1,v.length?p=v.concat(p):y=-1,p.length&&c())}function c(){if(!d){var t=i(u);d=!0;for(var n=p.length;n;){for(v=p,p=[];++y<n;)v&&v[y].run();y=-1,n=p.length}v=null,d=!1,o(t)}}function f(t,n){this.fun=t,this.array=n}function a(){}var s,l,h=t.exports={};!function(){try{s="function"==typeof setTimeout?setTimeout:r}catch(t){s=r}try{l="function"==typeof clearTimeout?clearTimeout:e}catch(t){l=e}}();var v,p=[],d=!1,y=-1;h.nextTick=function(t){var n=new Array(arguments.length-1);if(arguments.length>1)for(var r=1;r<arguments.length;r++)n[r-1]=arguments[r];p.push(new f(t,n)),1!==p.length||d||i(c)},f.prototype.run=function(){this.fun.apply(null,this.array)},h.title="browser",h.browser=!0,h.env={},h.argv=[],h.version="",h.versions={},h.on=a,h.addListener=a,h.once=a,h.off=a,h.removeListener=a,h.removeAllListeners=a,h.emit=a,h.prependListener=a,h.prependOnceListener=a,h.listeners=function(t){return[]},h.binding=function(t){throw new Error("process.binding is not supported")},h.cwd=function(){return"/"},h.chdir=function(t){throw new Error("process.chdir is not supported")},h.umask=function(){return 0}},function(t,n,r){var e=r(45);t.exports=function(t,n){if("number"!=typeof t&&"Number"!=e(t))throw TypeError(n);return+t}},function(t,n,r){"use strict";var e=r(17),i=r(75),o=r(16);t.exports=[].copyWithin||function(t,n){var r=e(this),u=o(r.length),c=i(t,u),f=i(n,u),a=arguments.length>2?arguments[2]:void 0,s=Math.min((void 0===a?u:i(a,u))-f,u-c),l=1;for(f<c&&c<f+s&&(l=-1,f+=s-1,c+=s-1);s-- >0;)f in r?r[c]=r[f]:delete r[c],c+=l,f+=l;return r}},function(t,n,r){var e=r(79);t.exports=function(t,n){var r=[];return e(t,!1,r.push,r,n),r}},function(t,n,r){var e=r(26),i=r(17),o=r(115),u=r(16);t.exports=function(t,n,r,c,f){e(n);var a=i(t),s=o(a),l=u(a.length),h=f?l-1:0,v=f?-1:1;if(r<2)for(;;){if(h in s){c=s[h],h+=v;break}if(h+=v,f?h<0:l<=h)throw TypeError("Reduce of empty array with no initial value")}for(;f?h>=0:l>h;h+=v)h in s&&(c=n(c,s[h],h,a));return c}},function(t,n,r){"use strict";var e=r(26),i=r(6),o=r(121),u=[].slice,c={},f=function(t,n,r){if(!(n in c)){for(var e=[],i=0;i<n;i++)e[i]="a["+i+"]";c[n]=Function("F,a","return new F("+e.join(",")+")")}return c[n](t,r)};t.exports=Function.bind||function(t){var n=e(this),r=u.call(arguments,1),c=function(){var e=r.concat(u.call(arguments));return this instanceof c?f(n,e.length,e):o(n,e,t)};return i(n.prototype)&&(c.prototype=n.prototype),c}},function(t,n,r){"use strict";var e=r(11).f,i=r(70),o=r(73),u=r(53),c=r(68),f=r(46),a=r(79),s=r(140),l=r(170),h=r(74),v=r(10),p=r(65).fastKey,d=v?"_s":"size",y=function(t,n){var r,e=p(n);if("F"!==e)return t._i[e];for(r=t._f;r;r=r.n)if(r.k==n)return r};t.exports={getConstructor:function(t,n,r,s){var l=t(function(t,e){c(t,l,n,"_i"),t._i=i(null),t._f=void 0,t._l=void 0,t[d]=0,void 0!=e&&a(e,r,t[s],t)});return o(l.prototype,{clear:function(){for(var t=this,n=t._i,r=t._f;r;r=r.n)r.r=!0,r.p&&(r.p=r.p.n=void 0),delete n[r.i];t._f=t._l=void 0,t[d]=0},delete:function(t){var n=this,r=y(n,t);if(r){var e=r.n,i=r.p;delete n._i[r.i],r.r=!0,i&&(i.n=e),e&&(e.p=i),n._f==r&&(n._f=e),n._l==r&&(n._l=i),n[d]--}return!!r},forEach:function(t){c(this,l,"forEach");for(var n,r=u(t,arguments.length>1?arguments[1]:void 0,3);n=n?n.n:this._f;)for(r(n.v,n.k,this);n&&n.r;)n=n.p},has:function(t){return!!y(this,t)}}),v&&e(l.prototype,"size",{get:function(){return f(this[d])}}),l},def:function(t,n,r){var e,i,o=y(t,n);return o?o.v=r:(t._l=o={i:i=p(n,!0),k:n,v:r,p:e=t._l,n:void 0,r:!1},t._f||(t._f=o),e&&(e.n=o),t[d]++,"F"!==i&&(t._i[i]=o)),t},getEntry:y,setStrong:function(t,n,r){s(t,n,function(t,n){this._t=t,this._k=n,this._l=void 0},function(){for(var t=this,n=t._k,r=t._l;r&&r.r;)r=r.p;return t._t&&(t._l=r=r?r.n:t._t._f)?"keys"==n?l(0,r.k):"values"==n?l(0,r.v):l(0,[r.k,r.v]):(t._t=void 0,l(1))},r?"entries":"values",!r,!0),h(n)}}},function(t,n,r){var e=r(114),i=r(161);t.exports=function(t){return function(){if(e(this)!=t)throw TypeError(t+"#toJSON isn't generic");return i(this)}}},function(t,n,r){"use strict";var e=r(73),i=r(65).getWeak,o=r(2),u=r(6),c=r(68),f=r(79),a=r(48),s=r(24),l=a(5),h=a(6),v=0,p=function(t){return t._l||(t._l=new d)},d=function(){this.a=[]},y=function(t,n){return l(t.a,function(t){return t[0]===n})};d.prototype={get:function(t){var n=y(this,t);if(n)return n[1]},has:function(t){return!!y(this,t)},set:function(t,n){var r=y(this,t);r?r[1]=n:this.a.push([t,n])},delete:function(t){var n=h(this.a,function(n){return n[0]===t});return~n&&this.a.splice(n,1),!!~n}},t.exports={getConstructor:function(t,n,r,o){var a=t(function(t,e){c(t,a,n,"_i"),t._i=v++,t._l=void 0,void 0!=e&&f(e,r,t[o],t)});return e(a.prototype,{delete:function(t){if(!u(t))return!1;var n=i(t);return!0===n?p(this).delete(t):n&&s(n,this._i)&&delete n[this._i]},has:function(t){if(!u(t))return!1;var n=i(t);return!0===n?p(this).has(t):n&&s(n,this._i)}}),a},def:function(t,n,r){var e=i(o(n),!0);return!0===e?p(t).set(n,r):e[t._i]=r,t},ufstore:p}},function(t,n,r){t.exports=!r(10)&&!r(4)(function(){return 7!=Object.defineProperty(r(132)("div"),"a",{get:function(){return 7}}).a})},function(t,n,r){var e=r(6),i=Math.floor;t.exports=function(t){return!e(t)&&isFinite(t)&&i(t)===t}},function(t,n,r){var e=r(2);t.exports=function(t,n,r,i){try{return i?n(e(r)[0],r[1]):n(r)}catch(n){var o=t.return;throw void 0!==o&&e(o.call(t)),n}}},function(t,n){t.exports=function(t,n){return{value:n,done:!!t}}},function(t,n){t.exports=Math.log1p||function(t){return(t=+t)>-1e-8&&t<1e-8?t-t*t/2:Math.log(1+t)}},function(t,n,r){"use strict";var e=r(72),i=r(125),o=r(116),u=r(17),c=r(115),f=Object.assign;t.exports=!f||r(4)(function(){var t={},n={},r=Symbol(),e="abcdefghijklmnopqrst";return t[r]=7,e.split("").forEach(function(t){n[t]=t}),7!=f({},t)[r]||Object.keys(f({},n)).join("")!=e})?function(t,n){for(var r=u(t),f=arguments.length,a=1,s=i.f,l=o.f;f>a;)for(var h,v=c(arguments[a++]),p=s?e(v).concat(s(v)):e(v),d=p.length,y=0;d>y;)l.call(v,h=p[y++])&&(r[h]=v[h]);return r}:f},function(t,n,r){var e=r(11),i=r(2),o=r(72);t.exports=r(10)?Object.defineProperties:function(t,n){i(t);for(var r,u=o(n),c=u.length,f=0;c>f;)e.f(t,r=u[f++],n[r]);return t}},function(t,n,r){var e=r(30),i=r(71).f,o={}.toString,u="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],c=function(t){try{return i(t)}catch(t){return u.slice()}};t.exports.f=function(t){return u&&"[object Window]"==o.call(t)?c(t):i(e(t))}},function(t,n,r){var e=r(24),i=r(30),o=r(117)(!1),u=r(145)("IE_PROTO");t.exports=function(t,n){var r,c=i(t),f=0,a=[];for(r in c)r!=u&&e(c,r)&&a.push(r);for(;n.length>f;)e(c,r=n[f++])&&(~o(a,r)||a.push(r));return a}},function(t,n,r){var e=r(72),i=r(30),o=r(116).f;t.exports=function(t){return function(n){for(var r,u=i(n),c=e(u),f=c.length,a=0,s=[];f>a;)o.call(u,r=c[a++])&&s.push(t?[r,u[r]]:u[r]);return s}}},function(t,n,r){var e=r(71),i=r(125),o=r(2),u=r(3).Reflect;t.exports=u&&u.ownKeys||function(t){var n=e.f(o(t)),r=i.f;return r?n.concat(r(t)):n}},function(t,n,r){var e=r(3).parseFloat,i=r(82).trim;t.exports=1/e(r(150)+"-0")!=-1/0?function(t){var n=i(String(t),3),r=e(n);return 0===r&&"-"==n.charAt(0)?-0:r}:e},function(t,n,r){var e=r(3).parseInt,i=r(82).trim,o=r(150),u=/^[\-+]?0[xX]/;t.exports=8!==e(o+"08")||22!==e(o+"0x16")?function(t,n){var r=i(String(t),3);return e(r,n>>>0||(u.test(r)?16:10))}:e},function(t,n){t.exports=Object.is||function(t,n){return t===n?0!==t||1/t==1/n:t!=t&&n!=n}},function(t,n,r){var e=r(16),i=r(149),o=r(46);t.exports=function(t,n,r,u){var c=String(o(t)),f=c.length,a=void 0===r?" ":String(r),s=e(n);if(s<=f||""==a)return c;var l=s-f,h=i.call(a,Math.ceil(l/a.length));return h.length>l&&(h=h.slice(0,l)),u?h+c:c+h}},function(t,n,r){n.f=r(7)},function(t,n,r){"use strict";var e=r(164);t.exports=r(118)("Map",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{get:function(t){var n=e.getEntry(this,t);return n&&n.v},set:function(t,n){return e.def(this,0===t?0:t,n)}},e,!0)},function(t,n,r){r(10)&&"g"!=/./g.flags&&r(11).f(RegExp.prototype,"flags",{configurable:!0,get:r(120)})},function(t,n,r){"use strict";var e=r(164);t.exports=r(118)("Set",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(this,t=0===t?0:t,t)}},e)},function(t,n,r){"use strict";var e,i=r(48)(0),o=r(28),u=r(65),c=r(172),f=r(166),a=r(6),s=u.getWeak,l=Object.isExtensible,h=f.ufstore,v={},p=function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},d={get:function(t){if(a(t)){var n=s(t);return!0===n?h(this).get(t):n?n[this._i]:void 0}},set:function(t,n){return f.def(this,t,n)}},y=t.exports=r(118)("WeakMap",p,d,f,!0,!0);7!=(new y).set((Object.freeze||Object)(v),7).get(v)&&(e=f.getConstructor(p),c(e.prototype,d),u.NEED=!0,i(["delete","has","get","set"],function(t){var n=y.prototype,r=n[t];o(n,t,function(n,i){if(a(n)&&!l(n)){this._f||(this._f=new e);var o=this._f[t](n,i);return"set"==t?this:o}return r.call(this,n,i)})}))},,,,function(t,n){"use strict";function r(){var t=document.querySelector("#page-nav");if(t&&!document.querySelector("#page-nav .extend.prev")&&(t.innerHTML='<a class="extend prev disabled" rel="prev">&laquo; Prev</a>'+t.innerHTML),t&&!document.querySelector("#page-nav .extend.next")&&(t.innerHTML=t.innerHTML+'<a class="extend next disabled" rel="next">Next &raquo;</a>'),yiliaConfig&&yiliaConfig.open_in_new){document.querySelectorAll(".article-entry a:not(.article-more-a)").forEach(function(t){var n=t.getAttribute("target");n&&""!==n||t.setAttribute("target","_blank")})}if(yiliaConfig&&yiliaConfig.toc_hide_index){document.querySelectorAll(".toc-number").forEach(function(t){t.style.display="none"})}var n=document.querySelector("#js-aboutme");n&&0!==n.length&&(n.innerHTML=n.innerText)}t.exports={init:r}},function(t,n,r){"use strict";function e(t){return t&&t.__esModule?t:{default:t}}function i(t,n){var r=/\/|index.html/g;return t.replace(r,"")===n.replace(r,"")}function o(){for(var t=document.querySelectorAll(".js-header-menu li a"),n=window.location.pathname,r=0,e=t.length;r<e;r++){var o=t[r];i(n,o.getAttribute("href"))&&(0,h.default)(o,"active")}}function u(t){for(var n=t.offsetLeft,r=t.offsetParent;null!==r;)n+=r.offsetLeft,r=r.offsetParent;return n}function c(t){for(var n=t.offsetTop,r=t.offsetParent;null!==r;)n+=r.offsetTop,r=r.offsetParent;return n}function f(t,n,r,e,i){var o=u(t),f=c(t)-n;if(f-r<=i){var a=t.$newDom;a||(a=t.cloneNode(!0),(0,d.default)(t,a),t.$newDom=a,a.style.position="fixed",a.style.top=(r||f)+"px",a.style.left=o+"px",a.style.zIndex=e||2,a.style.width="100%",a.style.color="#fff"),a.style.visibility="visible",t.style.visibility="hidden"}else{t.style.visibility="visible";var s=t.$newDom;s&&(s.style.visibility="hidden")}}function a(){var t=document.querySelector(".js-overlay"),n=document.querySelector(".js-header-menu");f(t,document.body.scrollTop,-63,2,0),f(n,document.body.scrollTop,1,3,0)}function s(){document.querySelector("#container").addEventListener("scroll",function(t){a()}),window.addEventListener("scroll",function(t){a()}),a()}var l=r(156),h=e(l),v=r(157),p=(e(v),r(382)),d=e(p),y=r(128),g=e(y),b=r(190),m=e(b),x=r(129);(function(){g.default.versions.mobile&&window.screen.width<800&&(o(),s())})(),(0,x.addLoadEvent)(function(){m.default.init()}),t.exports={}},,,,function(t,n,r){(function(t){"use strict";function n(t,n,r){t[n]||Object[e](t,n,{writable:!0,configurable:!0,value:r})}if(r(381),r(391),r(198),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0;var e="defineProperty";n(String.prototype,"padLeft","".padStart),n(String.prototype,"padRight","".padEnd),"pop,reverse,shift,keys,values,entries,indexOf,every,some,forEach,map,filter,find,findIndex,includes,join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill".split(",").forEach(function(t){[][t]&&n(Array,t,Function.call.bind([][t]))})}).call(n,function(){return this}())},,,function(t,n,r){r(210),t.exports=r(52).RegExp.escape},,,,function(t,n,r){var e=r(6),i=r(138),o=r(7)("species");t.exports=function(t){var n;return i(t)&&(n=t.constructor,"function"!=typeof n||n!==Array&&!i(n.prototype)||(n=void 0),e(n)&&null===(n=n[o])&&(n=void 0)),void 0===n?Array:n}},function(t,n,r){var e=r(202);t.exports=function(t,n){return new(e(t))(n)}},function(t,n,r){"use strict";var e=r(2),i=r(50),o="number";t.exports=function(t){if("string"!==t&&t!==o&&"default"!==t)throw TypeError("Incorrect hint");return i(e(this),t!=o)}},function(t,n,r){var e=r(72),i=r(125),o=r(116);t.exports=function(t){var n=e(t),r=i.f;if(r)for(var u,c=r(t),f=o.f,a=0;c.length>a;)f.call(t,u=c[a++])&&n.push(u);return n}},function(t,n,r){var e=r(72),i=r(30);t.exports=function(t,n){for(var r,o=i(t),u=e(o),c=u.length,f=0;c>f;)if(o[r=u[f++]]===n)return r}},function(t,n,r){"use strict";var e=r(208),i=r(121),o=r(26);t.exports=function(){for(var t=o(this),n=arguments.length,r=Array(n),u=0,c=e._,f=!1;n>u;)(r[u]=arguments[u++])===c&&(f=!0);return function(){var e,o=this,u=arguments.length,a=0,s=0;if(!f&&!u)return i(t,r,o);if(e=r.slice(),f)for(;n>a;a++)e[a]===c&&(e[a]=arguments[s++]);for(;u>s;)e.push(arguments[s++]);return i(t,e,o)}}},function(t,n,r){t.exports=r(3)},function(t,n){t.exports=function(t,n){var r=n===Object(n)?function(t){return n[t]}:n;return function(n){return String(n).replace(t,r)}}},function(t,n,r){var e=r(1),i=r(209)(/[\\^$*+?.()|[\]{}]/g,"\\$&");e(e.S,"RegExp",{escape:function(t){return i(t)}})},function(t,n,r){var e=r(1);e(e.P,"Array",{copyWithin:r(160)}),r(78)("copyWithin")},function(t,n,r){"use strict";var e=r(1),i=r(48)(4);e(e.P+e.F*!r(47)([].every,!0),"Array",{every:function(t){return i(this,t,arguments[1])}})},function(t,n,r){var e=r(1);e(e.P,"Array",{fill:r(130)}),r(78)("fill")},function(t,n,r){"use strict";var e=r(1),i=r(48)(2);e(e.P+e.F*!r(47)([].filter,!0),"Array",{filter:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(48)(6),o="findIndex",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{findIndex:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(78)(o)},function(t,n,r){"use strict";var e=r(1),i=r(48)(5),o="find",u=!0;o in[]&&Array(1)[o](function(){u=!1}),e(e.P+e.F*u,"Array",{find:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(78)(o)},function(t,n,r){"use strict";var e=r(1),i=r(48)(0),o=r(47)([].forEach,!0);e(e.P+e.F*!o,"Array",{forEach:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(53),i=r(1),o=r(17),u=r(169),c=r(137),f=r(16),a=r(131),s=r(154);i(i.S+i.F*!r(123)(function(t){Array.from(t)}),"Array",{from:function(t){var n,r,i,l,h=o(t),v="function"==typeof this?this:Array,p=arguments.length,d=p>1?arguments[1]:void 0,y=void 0!==d,g=0,b=s(h);if(y&&(d=e(d,p>2?arguments[2]:void 0,2)),void 0==b||v==Array&&c(b))for(n=f(h.length),r=new v(n);n>g;g++)a(r,g,y?d(h[g],g):h[g]);else for(l=b.call(h),r=new v;!(i=l.next()).done;g++)a(r,g,y?u(l,d,[i.value,g],!0):i.value);return r.length=g,r}})},function(t,n,r){"use strict";var e=r(1),i=r(117)(!1),o=[].indexOf,u=!!o&&1/[1].indexOf(1,-0)<0;e(e.P+e.F*(u||!r(47)(o)),"Array",{indexOf:function(t){return u?o.apply(this,arguments)||0:i(this,t,arguments[1])}})},function(t,n,r){var e=r(1);e(e.S,"Array",{isArray:r(138)})},function(t,n,r){"use strict";var e=r(1),i=r(30),o=[].join;e(e.P+e.F*(r(115)!=Object||!r(47)(o)),"Array",{join:function(t){return o.call(i(this),void 0===t?",":t)}})},function(t,n,r){"use strict";var e=r(1),i=r(30),o=r(67),u=r(16),c=[].lastIndexOf,f=!!c&&1/[1].lastIndexOf(1,-0)<0;e(e.P+e.F*(f||!r(47)(c)),"Array",{lastIndexOf:function(t){if(f)return c.apply(this,arguments)||0;var n=i(this),r=u(n.length),e=r-1;for(arguments.length>1&&(e=Math.min(e,o(arguments[1]))),e<0&&(e=r+e);e>=0;e--)if(e in n&&n[e]===t)return e||0;return-1}})},function(t,n,r){"use strict";var e=r(1),i=r(48)(1);e(e.P+e.F*!r(47)([].map,!0),"Array",{map:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(131);e(e.S+e.F*r(4)(function(){function t(){}return!(Array.of.call(t)instanceof t)}),"Array",{of:function(){for(var t=0,n=arguments.length,r=new("function"==typeof this?this:Array)(n);n>t;)i(r,t,arguments[t++]);return r.length=n,r}})},function(t,n,r){"use strict";var e=r(1),i=r(162);e(e.P+e.F*!r(47)([].reduceRight,!0),"Array",{reduceRight:function(t){return i(this,t,arguments.length,arguments[1],!0)}})},function(t,n,r){"use strict";var e=r(1),i=r(162);e(e.P+e.F*!r(47)([].reduce,!0),"Array",{reduce:function(t){return i(this,t,arguments.length,arguments[1],!1)}})},function(t,n,r){"use strict";var e=r(1),i=r(135),o=r(45),u=r(75),c=r(16),f=[].slice;e(e.P+e.F*r(4)(function(){i&&f.call(i)}),"Array",{slice:function(t,n){var r=c(this.length),e=o(this);if(n=void 0===n?r:n,"Array"==e)return f.call(this,t,n);for(var i=u(t,r),a=u(n,r),s=c(a-i),l=Array(s),h=0;h<s;h++)l[h]="String"==e?this.charAt(i+h):this[i+h];return l}})},function(t,n,r){"use strict";var e=r(1),i=r(48)(3);e(e.P+e.F*!r(47)([].some,!0),"Array",{some:function(t){return i(this,t,arguments[1])}})},function(t,n,r){"use strict";var e=r(1),i=r(26),o=r(17),u=r(4),c=[].sort,f=[1,2,3];e(e.P+e.F*(u(function(){f.sort(void 0)})||!u(function(){f.sort(null)})||!r(47)(c)),"Array",{sort:function(t){return void 0===t?c.call(o(this)):c.call(o(this),i(t))}})},function(t,n,r){r(74)("Array")},function(t,n,r){var e=r(1);e(e.S,"Date",{now:function(){return(new Date).getTime()}})},function(t,n,r){"use strict";var e=r(1),i=r(4),o=Date.prototype.getTime,u=function(t){return t>9?t:"0"+t};e(e.P+e.F*(i(function(){return"0385-07-25T07:06:39.999Z"!=new Date(-5e13-1).toISOString()})||!i(function(){new Date(NaN).toISOString()})),"Date",{toISOString:function(){
if(!isFinite(o.call(this)))throw RangeError("Invalid time value");var t=this,n=t.getUTCFullYear(),r=t.getUTCMilliseconds(),e=n<0?"-":n>9999?"+":"";return e+("00000"+Math.abs(n)).slice(e?-6:-4)+"-"+u(t.getUTCMonth()+1)+"-"+u(t.getUTCDate())+"T"+u(t.getUTCHours())+":"+u(t.getUTCMinutes())+":"+u(t.getUTCSeconds())+"."+(r>99?r:"0"+u(r))+"Z"}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(50);e(e.P+e.F*r(4)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(t){var n=i(this),r=o(n);return"number"!=typeof r||isFinite(r)?n.toISOString():null}})},function(t,n,r){var e=r(7)("toPrimitive"),i=Date.prototype;e in i||r(27)(i,e,r(204))},function(t,n,r){var e=Date.prototype,i="Invalid Date",o="toString",u=e[o],c=e.getTime;new Date(NaN)+""!=i&&r(28)(e,o,function(){var t=c.call(this);return t===t?u.call(this):i})},function(t,n,r){var e=r(1);e(e.P,"Function",{bind:r(163)})},function(t,n,r){"use strict";var e=r(6),i=r(32),o=r(7)("hasInstance"),u=Function.prototype;o in u||r(11).f(u,o,{value:function(t){if("function"!=typeof this||!e(t))return!1;if(!e(this.prototype))return t instanceof this;for(;t=i(t);)if(this.prototype===t)return!0;return!1}})},function(t,n,r){var e=r(11).f,i=r(66),o=r(24),u=Function.prototype,c="name",f=Object.isExtensible||function(){return!0};c in u||r(10)&&e(u,c,{configurable:!0,get:function(){try{var t=this,n=(""+t).match(/^\s*function ([^ (]*)/)[1];return o(t,c)||!f(t)||e(t,c,i(5,n)),n}catch(t){return""}}})},function(t,n,r){var e=r(1),i=r(171),o=Math.sqrt,u=Math.acosh;e(e.S+e.F*!(u&&710==Math.floor(u(Number.MAX_VALUE))&&u(1/0)==1/0),"Math",{acosh:function(t){return(t=+t)<1?NaN:t>94906265.62425156?Math.log(t)+Math.LN2:i(t-1+o(t-1)*o(t+1))}})},function(t,n,r){function e(t){return isFinite(t=+t)&&0!=t?t<0?-e(-t):Math.log(t+Math.sqrt(t*t+1)):t}var i=r(1),o=Math.asinh;i(i.S+i.F*!(o&&1/o(0)>0),"Math",{asinh:e})},function(t,n,r){var e=r(1),i=Math.atanh;e(e.S+e.F*!(i&&1/i(-0)<0),"Math",{atanh:function(t){return 0==(t=+t)?t:Math.log((1+t)/(1-t))/2}})},function(t,n,r){var e=r(1),i=r(142);e(e.S,"Math",{cbrt:function(t){return i(t=+t)*Math.pow(Math.abs(t),1/3)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{clz32:function(t){return(t>>>=0)?31-Math.floor(Math.log(t+.5)*Math.LOG2E):32}})},function(t,n,r){var e=r(1),i=Math.exp;e(e.S,"Math",{cosh:function(t){return(i(t=+t)+i(-t))/2}})},function(t,n,r){var e=r(1),i=r(141);e(e.S+e.F*(i!=Math.expm1),"Math",{expm1:i})},function(t,n,r){var e=r(1),i=r(142),o=Math.pow,u=o(2,-52),c=o(2,-23),f=o(2,127)*(2-c),a=o(2,-126),s=function(t){return t+1/u-1/u};e(e.S,"Math",{fround:function(t){var n,r,e=Math.abs(t),o=i(t);return e<a?o*s(e/a/c)*a*c:(n=(1+c/u)*e,r=n-(n-e),r>f||r!=r?o*(1/0):o*r)}})},function(t,n,r){var e=r(1),i=Math.abs;e(e.S,"Math",{hypot:function(t,n){for(var r,e,o=0,u=0,c=arguments.length,f=0;u<c;)r=i(arguments[u++]),f<r?(e=f/r,o=o*e*e+1,f=r):r>0?(e=r/f,o+=e*e):o+=r;return f===1/0?1/0:f*Math.sqrt(o)}})},function(t,n,r){var e=r(1),i=Math.imul;e(e.S+e.F*r(4)(function(){return-5!=i(4294967295,5)||2!=i.length}),"Math",{imul:function(t,n){var r=65535,e=+t,i=+n,o=r&e,u=r&i;return 0|o*u+((r&e>>>16)*u+o*(r&i>>>16)<<16>>>0)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{log10:function(t){return Math.log(t)/Math.LN10}})},function(t,n,r){var e=r(1);e(e.S,"Math",{log1p:r(171)})},function(t,n,r){var e=r(1);e(e.S,"Math",{log2:function(t){return Math.log(t)/Math.LN2}})},function(t,n,r){var e=r(1);e(e.S,"Math",{sign:r(142)})},function(t,n,r){var e=r(1),i=r(141),o=Math.exp;e(e.S+e.F*r(4)(function(){return-2e-17!=!Math.sinh(-2e-17)}),"Math",{sinh:function(t){return Math.abs(t=+t)<1?(i(t)-i(-t))/2:(o(t-1)-o(-t-1))*(Math.E/2)}})},function(t,n,r){var e=r(1),i=r(141),o=Math.exp;e(e.S,"Math",{tanh:function(t){var n=i(t=+t),r=i(-t);return n==1/0?1:r==1/0?-1:(n-r)/(o(t)+o(-t))}})},function(t,n,r){var e=r(1);e(e.S,"Math",{trunc:function(t){return(t>0?Math.floor:Math.ceil)(t)}})},function(t,n,r){"use strict";var e=r(3),i=r(24),o=r(45),u=r(136),c=r(50),f=r(4),a=r(71).f,s=r(31).f,l=r(11).f,h=r(82).trim,v="Number",p=e[v],d=p,y=p.prototype,g=o(r(70)(y))==v,b="trim"in String.prototype,m=function(t){var n=c(t,!1);if("string"==typeof n&&n.length>2){n=b?n.trim():h(n,3);var r,e,i,o=n.charCodeAt(0);if(43===o||45===o){if(88===(r=n.charCodeAt(2))||120===r)return NaN}else if(48===o){switch(n.charCodeAt(1)){case 66:case 98:e=2,i=49;break;case 79:case 111:e=8,i=55;break;default:return+n}for(var u,f=n.slice(2),a=0,s=f.length;a<s;a++)if((u=f.charCodeAt(a))<48||u>i)return NaN;return parseInt(f,e)}}return+n};if(!p(" 0o1")||!p("0b1")||p("+0x1")){p=function(t){var n=arguments.length<1?0:t,r=this;return r instanceof p&&(g?f(function(){y.valueOf.call(r)}):o(r)!=v)?u(new d(m(n)),r,p):m(n)};for(var x,w=r(10)?a(d):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),S=0;w.length>S;S++)i(d,x=w[S])&&!i(p,x)&&l(p,x,s(d,x));p.prototype=y,y.constructor=p,r(28)(e,v,p)}},function(t,n,r){var e=r(1);e(e.S,"Number",{EPSILON:Math.pow(2,-52)})},function(t,n,r){var e=r(1),i=r(3).isFinite;e(e.S,"Number",{isFinite:function(t){return"number"==typeof t&&i(t)}})},function(t,n,r){var e=r(1);e(e.S,"Number",{isInteger:r(168)})},function(t,n,r){var e=r(1);e(e.S,"Number",{isNaN:function(t){return t!=t}})},function(t,n,r){var e=r(1),i=r(168),o=Math.abs;e(e.S,"Number",{isSafeInteger:function(t){return i(t)&&o(t)<=9007199254740991}})},function(t,n,r){var e=r(1);e(e.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(t,n,r){var e=r(1);e(e.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(t,n,r){var e=r(1),i=r(178);e(e.S+e.F*(Number.parseFloat!=i),"Number",{parseFloat:i})},function(t,n,r){var e=r(1),i=r(179);e(e.S+e.F*(Number.parseInt!=i),"Number",{parseInt:i})},function(t,n,r){"use strict";var e=r(1),i=r(67),o=r(159),u=r(149),c=1..toFixed,f=Math.floor,a=[0,0,0,0,0,0],s="Number.toFixed: incorrect invocation!",l="0",h=function(t,n){for(var r=-1,e=n;++r<6;)e+=t*a[r],a[r]=e%1e7,e=f(e/1e7)},v=function(t){for(var n=6,r=0;--n>=0;)r+=a[n],a[n]=f(r/t),r=r%t*1e7},p=function(){for(var t=6,n="";--t>=0;)if(""!==n||0===t||0!==a[t]){var r=String(a[t]);n=""===n?r:n+u.call(l,7-r.length)+r}return n},d=function(t,n,r){return 0===n?r:n%2==1?d(t,n-1,r*t):d(t*t,n/2,r)},y=function(t){for(var n=0,r=t;r>=4096;)n+=12,r/=4096;for(;r>=2;)n+=1,r/=2;return n};e(e.P+e.F*(!!c&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!r(4)(function(){c.call({})})),"Number",{toFixed:function(t){var n,r,e,c,f=o(this,s),a=i(t),g="",b=l;if(a<0||a>20)throw RangeError(s);if(f!=f)return"NaN";if(f<=-1e21||f>=1e21)return String(f);if(f<0&&(g="-",f=-f),f>1e-21)if(n=y(f*d(2,69,1))-69,r=n<0?f*d(2,-n,1):f/d(2,n,1),r*=4503599627370496,(n=52-n)>0){for(h(0,r),e=a;e>=7;)h(1e7,0),e-=7;for(h(d(10,e,1),0),e=n-1;e>=23;)v(1<<23),e-=23;v(1<<e),h(1,1),v(2),b=p()}else h(0,r),h(1<<-n,0),b=p()+u.call(l,a);return a>0?(c=b.length,b=g+(c<=a?"0."+u.call(l,a-c)+b:b.slice(0,c-a)+"."+b.slice(c-a))):b=g+b,b}})},function(t,n,r){"use strict";var e=r(1),i=r(4),o=r(159),u=1..toPrecision;e(e.P+e.F*(i(function(){return"1"!==u.call(1,void 0)})||!i(function(){u.call({})})),"Number",{toPrecision:function(t){var n=o(this,"Number#toPrecision: incorrect invocation!");return void 0===t?u.call(n):u.call(n,t)}})},function(t,n,r){var e=r(1);e(e.S+e.F,"Object",{assign:r(172)})},function(t,n,r){var e=r(1);e(e.S,"Object",{create:r(70)})},function(t,n,r){var e=r(1);e(e.S+e.F*!r(10),"Object",{defineProperties:r(173)})},function(t,n,r){var e=r(1);e(e.S+e.F*!r(10),"Object",{defineProperty:r(11).f})},function(t,n,r){var e=r(6),i=r(65).onFreeze;r(49)("freeze",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(30),i=r(31).f;r(49)("getOwnPropertyDescriptor",function(){return function(t,n){return i(e(t),n)}})},function(t,n,r){r(49)("getOwnPropertyNames",function(){return r(174).f})},function(t,n,r){var e=r(17),i=r(32);r(49)("getPrototypeOf",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(6);r(49)("isExtensible",function(t){return function(n){return!!e(n)&&(!t||t(n))}})},function(t,n,r){var e=r(6);r(49)("isFrozen",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(6);r(49)("isSealed",function(t){return function(n){return!e(n)||!!t&&t(n)}})},function(t,n,r){var e=r(1);e(e.S,"Object",{is:r(180)})},function(t,n,r){var e=r(17),i=r(72);r(49)("keys",function(){return function(t){return i(e(t))}})},function(t,n,r){var e=r(6),i=r(65).onFreeze;r(49)("preventExtensions",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(6),i=r(65).onFreeze;r(49)("seal",function(t){return function(n){return t&&e(n)?t(i(n)):n}})},function(t,n,r){var e=r(1);e(e.S,"Object",{setPrototypeOf:r(144).set})},function(t,n,r){"use strict";var e=r(114),i={};i[r(7)("toStringTag")]="z",i+""!="[object z]"&&r(28)(Object.prototype,"toString",function(){return"[object "+e(this)+"]"},!0)},function(t,n,r){var e=r(1),i=r(178);e(e.G+e.F*(parseFloat!=i),{parseFloat:i})},function(t,n,r){var e=r(1),i=r(179);e(e.G+e.F*(parseInt!=i),{parseInt:i})},function(t,n,r){"use strict";var e,i,o,u=r(69),c=r(3),f=r(53),a=r(114),s=r(1),l=r(6),h=r(26),v=r(68),p=r(79),d=r(146),y=r(151).set,g=r(143)(),b="Promise",m=c.TypeError,x=c.process,w=c[b],x=c.process,S="process"==a(x),_=function(){},O=!!function(){try{var t=w.resolve(1),n=(t.constructor={})[r(7)("species")]=function(t){t(_,_)};return(S||"function"==typeof PromiseRejectionEvent)&&t.then(_)instanceof n}catch(t){}}(),E=function(t,n){return t===n||t===w&&n===o},P=function(t){var n;return!(!l(t)||"function"!=typeof(n=t.then))&&n},j=function(t){return E(w,t)?new F(t):new i(t)},F=i=function(t){var n,r;this.promise=new t(function(t,e){if(void 0!==n||void 0!==r)throw m("Bad Promise constructor");n=t,r=e}),this.resolve=h(n),this.reject=h(r)},M=function(t){try{t()}catch(t){return{error:t}}},A=function(t,n){if(!t._n){t._n=!0;var r=t._c;g(function(){for(var e=t._v,i=1==t._s,o=0;r.length>o;)!function(n){var r,o,u=i?n.ok:n.fail,c=n.resolve,f=n.reject,a=n.domain;try{u?(i||(2==t._h&&I(t),t._h=1),!0===u?r=e:(a&&a.enter(),r=u(e),a&&a.exit()),r===n.promise?f(m("Promise-chain cycle")):(o=P(r))?o.call(r,c,f):c(r)):f(e)}catch(t){f(t)}}(r[o++]);t._c=[],t._n=!1,n&&!t._h&&N(t)})}},N=function(t){y.call(c,function(){var n,r,e,i=t._v;if(T(t)&&(n=M(function(){S?x.emit("unhandledRejection",i,t):(r=c.onunhandledrejection)?r({promise:t,reason:i}):(e=c.console)&&e.error&&e.error("Unhandled promise rejection",i)}),t._h=S||T(t)?2:1),t._a=void 0,n)throw n.error})},T=function(t){if(1==t._h)return!1;for(var n,r=t._a||t._c,e=0;r.length>e;)if(n=r[e++],n.fail||!T(n.promise))return!1;return!0},I=function(t){y.call(c,function(){var n;S?x.emit("rejectionHandled",t):(n=c.onrejectionhandled)&&n({promise:t,reason:t._v})})},k=function(t){var n=this;n._d||(n._d=!0,n=n._w||n,n._v=t,n._s=2,n._a||(n._a=n._c.slice()),A(n,!0))},L=function(t){var n,r=this;if(!r._d){r._d=!0,r=r._w||r;try{if(r===t)throw m("Promise can't be resolved itself");(n=P(t))?g(function(){var e={_w:r,_d:!1};try{n.call(t,f(L,e,1),f(k,e,1))}catch(t){k.call(e,t)}}):(r._v=t,r._s=1,A(r,!1))}catch(t){k.call({_w:r,_d:!1},t)}}};O||(w=function(t){v(this,w,b,"_h"),h(t),e.call(this);try{t(f(L,this,1),f(k,this,1))}catch(t){k.call(this,t)}},e=function(t){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},e.prototype=r(73)(w.prototype,{then:function(t,n){var r=j(d(this,w));return r.ok="function"!=typeof t||t,r.fail="function"==typeof n&&n,r.domain=S?x.domain:void 0,this._c.push(r),this._a&&this._a.push(r),this._s&&A(this,!1),r.promise},catch:function(t){return this.then(void 0,t)}}),F=function(){var t=new e;this.promise=t,this.resolve=f(L,t,1),this.reject=f(k,t,1)}),s(s.G+s.W+s.F*!O,{Promise:w}),r(81)(w,b),r(74)(b),o=r(52)[b],s(s.S+s.F*!O,b,{reject:function(t){var n=j(this);return(0,n.reject)(t),n.promise}}),s(s.S+s.F*(u||!O),b,{resolve:function(t){if(t instanceof w&&E(t.constructor,this))return t;var n=j(this);return(0,n.resolve)(t),n.promise}}),s(s.S+s.F*!(O&&r(123)(function(t){w.all(t).catch(_)})),b,{all:function(t){var n=this,r=j(n),e=r.resolve,i=r.reject,o=M(function(){var r=[],o=0,u=1;p(t,!1,function(t){var c=o++,f=!1;r.push(void 0),u++,n.resolve(t).then(function(t){f||(f=!0,r[c]=t,--u||e(r))},i)}),--u||e(r)});return o&&i(o.error),r.promise},race:function(t){var n=this,r=j(n),e=r.reject,i=M(function(){p(t,!1,function(t){n.resolve(t).then(r.resolve,e)})});return i&&e(i.error),r.promise}})},function(t,n,r){var e=r(1),i=r(26),o=r(2),u=(r(3).Reflect||{}).apply,c=Function.apply;e(e.S+e.F*!r(4)(function(){u(function(){})}),"Reflect",{apply:function(t,n,r){var e=i(t),f=o(r);return u?u(e,n,f):c.call(e,n,f)}})},function(t,n,r){var e=r(1),i=r(70),o=r(26),u=r(2),c=r(6),f=r(4),a=r(163),s=(r(3).Reflect||{}).construct,l=f(function(){function t(){}return!(s(function(){},[],t)instanceof t)}),h=!f(function(){s(function(){})});e(e.S+e.F*(l||h),"Reflect",{construct:function(t,n){o(t),u(n);var r=arguments.length<3?t:o(arguments[2]);if(h&&!l)return s(t,n,r);if(t==r){switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3])}var e=[null];return e.push.apply(e,n),new(a.apply(t,e))}var f=r.prototype,v=i(c(f)?f:Object.prototype),p=Function.apply.call(t,v,n);return c(p)?p:v}})},function(t,n,r){var e=r(11),i=r(1),o=r(2),u=r(50);i(i.S+i.F*r(4)(function(){Reflect.defineProperty(e.f({},1,{value:1}),1,{value:2})}),"Reflect",{defineProperty:function(t,n,r){o(t),n=u(n,!0),o(r);try{return e.f(t,n,r),!0}catch(t){return!1}}})},function(t,n,r){var e=r(1),i=r(31).f,o=r(2);e(e.S,"Reflect",{deleteProperty:function(t,n){var r=i(o(t),n);return!(r&&!r.configurable)&&delete t[n]}})},function(t,n,r){"use strict";var e=r(1),i=r(2),o=function(t){this._t=i(t),this._i=0;var n,r=this._k=[];for(n in t)r.push(n)};r(139)(o,"Object",function(){var t,n=this,r=n._k;do{if(n._i>=r.length)return{value:void 0,done:!0}}while(!((t=r[n._i++])in n._t));return{value:t,done:!1}}),e(e.S,"Reflect",{enumerate:function(t){return new o(t)}})},function(t,n,r){var e=r(31),i=r(1),o=r(2);i(i.S,"Reflect",{getOwnPropertyDescriptor:function(t,n){return e.f(o(t),n)}})},function(t,n,r){var e=r(1),i=r(32),o=r(2);e(e.S,"Reflect",{getPrototypeOf:function(t){return i(o(t))}})},function(t,n,r){function e(t,n){var r,c,s=arguments.length<3?t:arguments[2];return a(t)===s?t[n]:(r=i.f(t,n))?u(r,"value")?r.value:void 0!==r.get?r.get.call(s):void 0:f(c=o(t))?e(c,n,s):void 0}var i=r(31),o=r(32),u=r(24),c=r(1),f=r(6),a=r(2);c(c.S,"Reflect",{get:e})},function(t,n,r){var e=r(1);e(e.S,"Reflect",{has:function(t,n){return n in t}})},function(t,n,r){var e=r(1),i=r(2),o=Object.isExtensible;e(e.S,"Reflect",{isExtensible:function(t){return i(t),!o||o(t)}})},function(t,n,r){var e=r(1);e(e.S,"Reflect",{ownKeys:r(177)})},function(t,n,r){var e=r(1),i=r(2),o=Object.preventExtensions;e(e.S,"Reflect",{preventExtensions:function(t){i(t);try{return o&&o(t),!0}catch(t){return!1}}})},function(t,n,r){var e=r(1),i=r(144);i&&e(e.S,"Reflect",{setPrototypeOf:function(t,n){i.check(t,n);try{return i.set(t,n),!0}catch(t){return!1}}})},function(t,n,r){function e(t,n,r){var f,h,v=arguments.length<4?t:arguments[3],p=o.f(s(t),n);if(!p){if(l(h=u(t)))return e(h,n,r,v);p=a(0)}return c(p,"value")?!(!1===p.writable||!l(v)||(f=o.f(v,n)||a(0),f.value=r,i.f(v,n,f),0)):void 0!==p.set&&(p.set.call(v,r),!0)}var i=r(11),o=r(31),u=r(32),c=r(24),f=r(1),a=r(66),s=r(2),l=r(6);f(f.S,"Reflect",{set:e})},function(t,n,r){var e=r(3),i=r(136),o=r(11).f,u=r(71).f,c=r(122),f=r(120),a=e.RegExp,s=a,l=a.prototype,h=/a/g,v=/a/g,p=new a(h)!==h;if(r(10)&&(!p||r(4)(function(){return v[r(7)("match")]=!1,a(h)!=h||a(v)==v||"/a/i"!=a(h,"i")}))){a=function(t,n){var r=this instanceof a,e=c(t),o=void 0===n;return!r&&e&&t.constructor===a&&o?t:i(p?new s(e&&!o?t.source:t,n):s((e=t instanceof a)?t.source:t,e&&o?f.call(t):n),r?this:l,a)};for(var d=u(s),y=0;d.length>y;)!function(t){t in a||o(a,t,{configurable:!0,get:function(){return s[t]},set:function(n){s[t]=n}})}(d[y++]);l.constructor=a,a.prototype=l,r(28)(e,"RegExp",a)}r(74)("RegExp")},function(t,n,r){r(119)("match",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=void 0==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(119)("replace",2,function(t,n,r){return[function(e,i){"use strict";var o=t(this),u=void 0==e?void 0:e[n];return void 0!==u?u.call(e,o,i):r.call(String(o),e,i)},r]})},function(t,n,r){r(119)("search",1,function(t,n,r){return[function(r){"use strict";var e=t(this),i=void 0==r?void 0:r[n];return void 0!==i?i.call(r,e):new RegExp(r)[n](String(e))},r]})},function(t,n,r){r(119)("split",2,function(t,n,e){"use strict";var i=r(122),o=e,u=[].push,c="split",f="length",a="lastIndex";if("c"=="abbc"[c](/(b)*/)[1]||4!="test"[c](/(?:)/,-1)[f]||2!="ab"[c](/(?:ab)*/)[f]||4!="."[c](/(.?)(.?)/)[f]||"."[c](/()()/)[f]>1||""[c](/.?/)[f]){var s=void 0===/()??/.exec("")[1];e=function(t,n){var r=String(this);if(void 0===t&&0===n)return[];if(!i(t))return o.call(r,t,n);var e,c,l,h,v,p=[],d=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),y=0,g=void 0===n?4294967295:n>>>0,b=new RegExp(t.source,d+"g");for(s||(e=new RegExp("^"+b.source+"$(?!\\s)",d));(c=b.exec(r))&&!((l=c.index+c[0][f])>y&&(p.push(r.slice(y,c.index)),!s&&c[f]>1&&c[0].replace(e,function(){for(v=1;v<arguments[f]-2;v++)void 0===arguments[v]&&(c[v]=void 0)}),c[f]>1&&c.index<r[f]&&u.apply(p,c.slice(1)),h=c[0][f],y=l,p[f]>=g));)b[a]===c.index&&b[a]++;return y===r[f]?!h&&b.test("")||p.push(""):p.push(r.slice(y)),p[f]>g?p.slice(0,g):p}}else"0"[c](void 0,0)[f]&&(e=function(t,n){return void 0===t&&0===n?[]:o.call(this,t,n)});return[function(r,i){var o=t(this),u=void 0==r?void 0:r[n];return void 0!==u?u.call(r,o,i):e.call(String(o),r,i)},e]})},function(t,n,r){"use strict";r(184);var e=r(2),i=r(120),o=r(10),u="toString",c=/./[u],f=function(t){r(28)(RegExp.prototype,u,t,!0)};r(4)(function(){return"/a/b"!=c.call({source:"a",flags:"b"})})?f(function(){var t=e(this);return"/".concat(t.source,"/","flags"in t?t.flags:!o&&t instanceof RegExp?i.call(t):void 0)}):c.name!=u&&f(function(){return c.call(this)})},function(t,n,r){"use strict";r(29)("anchor",function(t){return function(n){return t(this,"a","name",n)}})},function(t,n,r){"use strict";r(29)("big",function(t){return function(){return t(this,"big","","")}})},function(t,n,r){"use strict";r(29)("blink",function(t){return function(){return t(this,"blink","","")}})},function(t,n,r){"use strict";r(29)("bold",function(t){return function(){return t(this,"b","","")}})},function(t,n,r){"use strict";var e=r(1),i=r(147)(!1);e(e.P,"String",{codePointAt:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(1),i=r(16),o=r(148),u="endsWith",c=""[u];e(e.P+e.F*r(134)(u),"String",{endsWith:function(t){var n=o(this,t,u),r=arguments.length>1?arguments[1]:void 0,e=i(n.length),f=void 0===r?e:Math.min(i(r),e),a=String(t);return c?c.call(n,a,f):n.slice(f-a.length,f)===a}})},function(t,n,r){"use strict";r(29)("fixed",function(t){return function(){return t(this,"tt","","")}})},function(t,n,r){"use strict";r(29)("fontcolor",function(t){return function(n){return t(this,"font","color",n)}})},function(t,n,r){"use strict";r(29)("fontsize",function(t){return function(n){return t(this,"font","size",n)}})},function(t,n,r){var e=r(1),i=r(75),o=String.fromCharCode,u=String.fromCodePoint;e(e.S+e.F*(!!u&&1!=u.length),"String",{fromCodePoint:function(t){for(var n,r=[],e=arguments.length,u=0;e>u;){if(n=+arguments[u++],i(n,1114111)!==n)throw RangeError(n+" is not a valid code point");r.push(n<65536?o(n):o(55296+((n-=65536)>>10),n%1024+56320))}return r.join("")}})},function(t,n,r){"use strict";var e=r(1),i=r(148),o="includes";e(e.P+e.F*r(134)(o),"String",{includes:function(t){return!!~i(this,t,o).indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,n,r){"use strict";r(29)("italics",function(t){return function(){return t(this,"i","","")}})},function(t,n,r){"use strict";var e=r(147)(!0);r(140)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,r=this._i;return r>=n.length?{value:void 0,done:!0}:(t=e(n,r),this._i+=t.length,{value:t,done:!1})})},function(t,n,r){"use strict";r(29)("link",function(t){return function(n){return t(this,"a","href",n)}})},function(t,n,r){var e=r(1),i=r(30),o=r(16);e(e.S,"String",{raw:function(t){for(var n=i(t.raw),r=o(n.length),e=arguments.length,u=[],c=0;r>c;)u.push(String(n[c++])),c<e&&u.push(String(arguments[c]));return u.join("")}})},function(t,n,r){var e=r(1);e(e.P,"String",{repeat:r(149)})},function(t,n,r){"use strict";r(29)("small",function(t){return function(){return t(this,"small","","")}})},function(t,n,r){"use strict";var e=r(1),i=r(16),o=r(148),u="startsWith",c=""[u];e(e.P+e.F*r(134)(u),"String",{startsWith:function(t){var n=o(this,t,u),r=i(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),e=String(t);return c?c.call(n,e,r):n.slice(r,r+e.length)===e}})},function(t,n,r){"use strict";r(29)("strike",function(t){return function(){return t(this,"strike","","")}})},function(t,n,r){"use strict";r(29)("sub",function(t){return function(){return t(this,"sub","","")}})},function(t,n,r){"use strict";r(29)("sup",function(t){return function(){return t(this,"sup","","")}})},function(t,n,r){"use strict";r(82)("trim",function(t){return function(){return t(this,3)}})},function(t,n,r){"use strict";var e=r(3),i=r(24),o=r(10),u=r(1),c=r(28),f=r(65).KEY,a=r(4),s=r(126),l=r(81),h=r(76),v=r(7),p=r(182),d=r(153),y=r(206),g=r(205),b=r(138),m=r(2),x=r(30),w=r(50),S=r(66),_=r(70),O=r(174),E=r(31),P=r(11),j=r(72),F=E.f,M=P.f,A=O.f,N=e.Symbol,T=e.JSON,I=T&&T.stringify,k="prototype",L=v("_hidden"),R=v("toPrimitive"),C={}.propertyIsEnumerable,D=s("symbol-registry"),U=s("symbols"),W=s("op-symbols"),G=Object[k],B="function"==typeof N,V=e.QObject,z=!V||!V[k]||!V[k].findChild,q=o&&a(function(){return 7!=_(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(t,n,r){var e=F(G,n);e&&delete G[n],M(t,n,r),e&&t!==G&&M(G,n,e)}:M,K=function(t){var n=U[t]=_(N[k]);return n._k=t,n},J=B&&"symbol"==typeof N.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof N},Y=function(t,n,r){return t===G&&Y(W,n,r),m(t),n=w(n,!0),m(r),i(U,n)?(r.enumerable?(i(t,L)&&t[L][n]&&(t[L][n]=!1),r=_(r,{enumerable:S(0,!1)})):(i(t,L)||M(t,L,S(1,{})),t[L][n]=!0),q(t,n,r)):M(t,n,r)},H=function(t,n){m(t);for(var r,e=g(n=x(n)),i=0,o=e.length;o>i;)Y(t,r=e[i++],n[r]);return t},$=function(t,n){return void 0===n?_(t):H(_(t),n)},X=function(t){var n=C.call(this,t=w(t,!0));return!(this===G&&i(U,t)&&!i(W,t))&&(!(n||!i(this,t)||!i(U,t)||i(this,L)&&this[L][t])||n)},Q=function(t,n){if(t=x(t),n=w(n,!0),t!==G||!i(U,n)||i(W,n)){var r=F(t,n);return!r||!i(U,n)||i(t,L)&&t[L][n]||(r.enumerable=!0),r}},Z=function(t){for(var n,r=A(x(t)),e=[],o=0;r.length>o;)i(U,n=r[o++])||n==L||n==f||e.push(n);return e},tt=function(t){for(var n,r=t===G,e=A(r?W:x(t)),o=[],u=0;e.length>u;)!i(U,n=e[u++])||r&&!i(G,n)||o.push(U[n]);return o};B||(N=function(){if(this instanceof N)throw TypeError("Symbol is not a constructor!");var t=h(arguments.length>0?arguments[0]:void 0),n=function(r){this===G&&n.call(W,r),i(this,L)&&i(this[L],t)&&(this[L][t]=!1),q(this,t,S(1,r))};return o&&z&&q(G,t,{configurable:!0,set:n}),K(t)},c(N[k],"toString",function(){return this._k}),E.f=Q,P.f=Y,r(71).f=O.f=Z,r(116).f=X,r(125).f=tt,o&&!r(69)&&c(G,"propertyIsEnumerable",X,!0),p.f=function(t){return K(v(t))}),u(u.G+u.W+u.F*!B,{Symbol:N});for(var nt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),rt=0;nt.length>rt;)v(nt[rt++]);for(var nt=j(v.store),rt=0;nt.length>rt;)d(nt[rt++]);u(u.S+u.F*!B,"Symbol",{for:function(t){return i(D,t+="")?D[t]:D[t]=N(t)},keyFor:function(t){if(J(t))return y(D,t);throw TypeError(t+" is not a symbol!")},useSetter:function(){z=!0},useSimple:function(){z=!1}}),u(u.S+u.F*!B,"Object",{create:$,defineProperty:Y,defineProperties:H,getOwnPropertyDescriptor:Q,getOwnPropertyNames:Z,getOwnPropertySymbols:tt}),T&&u(u.S+u.F*(!B||a(function(){var t=N();return"[null]"!=I([t])||"{}"!=I({a:t})||"{}"!=I(Object(t))})),"JSON",{stringify:function(t){if(void 0!==t&&!J(t)){for(var n,r,e=[t],i=1;arguments.length>i;)e.push(arguments[i++]);return n=e[1],"function"==typeof n&&(r=n),!r&&b(n)||(n=function(t,n){if(r&&(n=r.call(this,t,n)),!J(n))return n}),e[1]=n,I.apply(T,e)}}}),N[k][R]||r(27)(N[k],R,N[k].valueOf),l(N,"Symbol"),l(Math,"Math",!0),l(e.JSON,"JSON",!0)},function(t,n,r){"use strict";var e=r(1),i=r(127),o=r(152),u=r(2),c=r(75),f=r(16),a=r(6),s=r(3).ArrayBuffer,l=r(146),h=o.ArrayBuffer,v=o.DataView,p=i.ABV&&s.isView,d=h.prototype.slice,y=i.VIEW,g="ArrayBuffer";e(e.G+e.W+e.F*(s!==h),{ArrayBuffer:h}),e(e.S+e.F*!i.CONSTR,g,{isView:function(t){return p&&p(t)||a(t)&&y in t}}),e(e.P+e.U+e.F*r(4)(function(){return!new h(2).slice(1,void 0).byteLength}),g,{slice:function(t,n){if(void 0!==d&&void 0===n)return d.call(u(this),t);for(var r=u(this).byteLength,e=c(t,r),i=c(void 0===n?r:n,r),o=new(l(this,h))(f(i-e)),a=new v(this),s=new v(o),p=0;e<i;)s.setUint8(p++,a.getUint8(e++));return o}}),r(74)(g)},function(t,n,r){var e=r(1);e(e.G+e.W+e.F*!r(127).ABV,{DataView:r(152).DataView})},function(t,n,r){r(55)("Float32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Float64",8,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Int16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Int32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Int8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint16",2,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint32",4,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}})},function(t,n,r){r(55)("Uint8",1,function(t){return function(n,r,e){return t(this,n,r,e)}},!0)},function(t,n,r){"use strict";var e=r(166);r(118)("WeakSet",function(t){return function(){return t(this,arguments.length>0?arguments[0]:void 0)}},{add:function(t){return e.def(this,t,!0)}},e,!1,!0)},function(t,n,r){"use strict";var e=r(1),i=r(117)(!0);e(e.P,"Array",{includes:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0)}}),r(78)("includes")},function(t,n,r){var e=r(1),i=r(143)(),o=r(3).process,u="process"==r(45)(o);e(e.G,{asap:function(t){var n=u&&o.domain;i(n?n.bind(t):t)}})},function(t,n,r){var e=r(1),i=r(45);e(e.S,"Error",{isError:function(t){return"Error"===i(t)}})},function(t,n,r){var e=r(1);e(e.P+e.R,"Map",{toJSON:r(165)("Map")})},function(t,n,r){var e=r(1);e(e.S,"Math",{iaddh:function(t,n,r,e){var i=t>>>0,o=n>>>0,u=r>>>0;return o+(e>>>0)+((i&u|(i|u)&~(i+u>>>0))>>>31)|0}})},function(t,n,r){var e=r(1);e(e.S,"Math",{imulh:function(t,n){var r=65535,e=+t,i=+n,o=e&r,u=i&r,c=e>>16,f=i>>16,a=(c*u>>>0)+(o*u>>>16);return c*f+(a>>16)+((o*f>>>0)+(a&r)>>16)}})},function(t,n,r){var e=r(1);e(e.S,"Math",{isubh:function(t,n,r,e){var i=t>>>0,o=n>>>0,u=r>>>0;return o-(e>>>0)-((~i&u|~(i^u)&i-u>>>0)>>>31)|0}})},function(t,n,r){var e=r(1);e(e.S,"Math",{umulh:function(t,n){var r=65535,e=+t,i=+n,o=e&r,u=i&r,c=e>>>16,f=i>>>16,a=(c*u>>>0)+(o*u>>>16);return c*f+(a>>>16)+((o*f>>>0)+(a&r)>>>16)}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(26),u=r(11);r(10)&&e(e.P+r(124),"Object",{__defineGetter__:function(t,n){u.f(i(this),t,{get:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(26),u=r(11);r(10)&&e(e.P+r(124),"Object",{__defineSetter__:function(t,n){u.f(i(this),t,{set:o(n),enumerable:!0,configurable:!0})}})},function(t,n,r){var e=r(1),i=r(176)(!0);e(e.S,"Object",{entries:function(t){return i(t)}})},function(t,n,r){var e=r(1),i=r(177),o=r(30),u=r(31),c=r(131);e(e.S,"Object",{getOwnPropertyDescriptors:function(t){for(var n,r=o(t),e=u.f,f=i(r),a={},s=0;f.length>s;)c(a,n=f[s++],e(r,n));return a}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(50),u=r(32),c=r(31).f;r(10)&&e(e.P+r(124),"Object",{__lookupGetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.get}while(r=u(r))}})},function(t,n,r){"use strict";var e=r(1),i=r(17),o=r(50),u=r(32),c=r(31).f;r(10)&&e(e.P+r(124),"Object",{__lookupSetter__:function(t){var n,r=i(this),e=o(t,!0);do{if(n=c(r,e))return n.set}while(r=u(r))}})},function(t,n,r){var e=r(1),i=r(176)(!1);e(e.S,"Object",{values:function(t){return i(t)}})},function(t,n,r){"use strict";var e=r(1),i=r(3),o=r(52),u=r(143)(),c=r(7)("observable"),f=r(26),a=r(2),s=r(68),l=r(73),h=r(27),v=r(79),p=v.RETURN,d=function(t){return null==t?void 0:f(t)},y=function(t){var n=t._c;n&&(t._c=void 0,n())},g=function(t){return void 0===t._o},b=function(t){g(t)||(t._o=void 0,y(t))},m=function(t,n){a(t),this._c=void 0,this._o=t,t=new x(this);try{var r=n(t),e=r;null!=r&&("function"==typeof r.unsubscribe?r=function(){e.unsubscribe()}:f(r),this._c=r)}catch(n){return void t.error(n)}g(this)&&y(this)};m.prototype=l({},{unsubscribe:function(){b(this)}});var x=function(t){this._s=t};x.prototype=l({},{next:function(t){var n=this._s;if(!g(n)){var r=n._o;try{var e=d(r.next);if(e)return e.call(r,t)}catch(t){try{b(n)}finally{throw t}}}},error:function(t){var n=this._s;if(g(n))throw t;var r=n._o;n._o=void 0;try{var e=d(r.error);if(!e)throw t;t=e.call(r,t)}catch(t){try{y(n)}finally{throw t}}return y(n),t},complete:function(t){var n=this._s;if(!g(n)){var r=n._o;n._o=void 0;try{var e=d(r.complete);t=e?e.call(r,t):void 0}catch(t){try{y(n)}finally{throw t}}return y(n),t}}});var w=function(t){s(this,w,"Observable","_f")._f=f(t)};l(w.prototype,{subscribe:function(t){return new m(t,this._f)},forEach:function(t){var n=this;return new(o.Promise||i.Promise)(function(r,e){f(t);var i=n.subscribe({next:function(n){try{return t(n)}catch(t){e(t),i.unsubscribe()}},error:e,complete:r})})}}),l(w,{from:function(t){var n="function"==typeof this?this:w,r=d(a(t)[c]);if(r){var e=a(r.call(t));return e.constructor===n?e:new n(function(t){return e.subscribe(t)})}return new n(function(n){var r=!1;return u(function(){if(!r){try{if(v(t,!1,function(t){if(n.next(t),r)return p})===p)return}catch(t){if(r)throw t;return void n.error(t)}n.complete()}}),function(){r=!0}})},of:function(){for(var t=0,n=arguments.length,r=Array(n);t<n;)r[t]=arguments[t++];return new("function"==typeof this?this:w)(function(t){var n=!1;return u(function(){if(!n){for(var e=0;e<r.length;++e)if(t.next(r[e]),n)return;t.complete()}}),function(){n=!0}})}}),h(w.prototype,c,function(){return this}),e(e.G,{Observable:w}),r(74)("Observable")},function(t,n,r){var e=r(54),i=r(2),o=e.key,u=e.set;e.exp({defineMetadata:function(t,n,r,e){u(t,n,i(r),o(e))}})},function(t,n,r){var e=r(54),i=r(2),o=e.key,u=e.map,c=e.store;e.exp({deleteMetadata:function(t,n){var r=arguments.length<3?void 0:o(arguments[2]),e=u(i(n),r,!1);if(void 0===e||!e.delete(t))return!1;if(e.size)return!0;var f=c.get(n);return f.delete(r),!!f.size||c.delete(n)}})},function(t,n,r){var e=r(185),i=r(161),o=r(54),u=r(2),c=r(32),f=o.keys,a=o.key,s=function(t,n){var r=f(t,n),o=c(t);if(null===o)return r;var u=s(o,n);return u.length?r.length?i(new e(r.concat(u))):u:r};o.exp({getMetadataKeys:function(t){return s(u(t),arguments.length<2?void 0:a(arguments[1]))}})},function(t,n,r){var e=r(54),i=r(2),o=r(32),u=e.has,c=e.get,f=e.key,a=function(t,n,r){if(u(t,n,r))return c(t,n,r);var e=o(n);return null!==e?a(t,e,r):void 0};e.exp({getMetadata:function(t,n){return a(t,i(n),arguments.length<3?void 0:f(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=e.keys,u=e.key;e.exp({getOwnMetadataKeys:function(t){
return o(i(t),arguments.length<2?void 0:u(arguments[1]))}})},function(t,n,r){var e=r(54),i=r(2),o=e.get,u=e.key;e.exp({getOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=r(32),u=e.has,c=e.key,f=function(t,n,r){if(u(t,n,r))return!0;var e=o(n);return null!==e&&f(t,e,r)};e.exp({hasMetadata:function(t,n){return f(t,i(n),arguments.length<3?void 0:c(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=e.has,u=e.key;e.exp({hasOwnMetadata:function(t,n){return o(t,i(n),arguments.length<3?void 0:u(arguments[2]))}})},function(t,n,r){var e=r(54),i=r(2),o=r(26),u=e.key,c=e.set;e.exp({metadata:function(t,n){return function(r,e){c(t,n,(void 0!==e?i:o)(r),u(e))}}})},function(t,n,r){var e=r(1);e(e.P+e.R,"Set",{toJSON:r(165)("Set")})},function(t,n,r){"use strict";var e=r(1),i=r(147)(!0);e(e.P,"String",{at:function(t){return i(this,t)}})},function(t,n,r){"use strict";var e=r(1),i=r(46),o=r(16),u=r(122),c=r(120),f=RegExp.prototype,a=function(t,n){this._r=t,this._s=n};r(139)(a,"RegExp String",function(){var t=this._r.exec(this._s);return{value:t,done:null===t}}),e(e.P,"String",{matchAll:function(t){if(i(this),!u(t))throw TypeError(t+" is not a regexp!");var n=String(this),r="flags"in f?String(t.flags):c.call(t),e=new RegExp(t.source,~r.indexOf("g")?r:"g"+r);return e.lastIndex=o(t.lastIndex),new a(e,n)}})},function(t,n,r){"use strict";var e=r(1),i=r(181);e(e.P,"String",{padEnd:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!1)}})},function(t,n,r){"use strict";var e=r(1),i=r(181);e(e.P,"String",{padStart:function(t){return i(this,t,arguments.length>1?arguments[1]:void 0,!0)}})},function(t,n,r){"use strict";r(82)("trimLeft",function(t){return function(){return t(this,1)}},"trimStart")},function(t,n,r){"use strict";r(82)("trimRight",function(t){return function(){return t(this,2)}},"trimEnd")},function(t,n,r){r(153)("asyncIterator")},function(t,n,r){r(153)("observable")},function(t,n,r){var e=r(1);e(e.S,"System",{global:r(3)})},function(t,n,r){for(var e=r(155),i=r(28),o=r(3),u=r(27),c=r(80),f=r(7),a=f("iterator"),s=f("toStringTag"),l=c.Array,h=["NodeList","DOMTokenList","MediaList","StyleSheetList","CSSRuleList"],v=0;v<5;v++){var p,d=h[v],y=o[d],g=y&&y.prototype;if(g){g[a]||u(g,a,l),g[s]||u(g,s,d),c[d]=l;for(p in e)g[p]||i(g,p,e[p],!0)}}},function(t,n,r){var e=r(1),i=r(151);e(e.G+e.B,{setImmediate:i.set,clearImmediate:i.clear})},function(t,n,r){var e=r(3),i=r(1),o=r(121),u=r(207),c=e.navigator,f=!!c&&/MSIE .\./.test(c.userAgent),a=function(t){return f?function(n,r){return t(o(u,[].slice.call(arguments,2),"function"==typeof n?n:Function(n)),r)}:t};i(i.G+i.B+i.F*f,{setTimeout:a(e.setTimeout),setInterval:a(e.setInterval)})},function(t,n,r){r(330),r(269),r(271),r(270),r(273),r(275),r(280),r(274),r(272),r(282),r(281),r(277),r(278),r(276),r(268),r(279),r(283),r(284),r(236),r(238),r(237),r(286),r(285),r(256),r(266),r(267),r(257),r(258),r(259),r(260),r(261),r(262),r(263),r(264),r(265),r(239),r(240),r(241),r(242),r(243),r(244),r(245),r(246),r(247),r(248),r(249),r(250),r(251),r(252),r(253),r(254),r(255),r(317),r(322),r(329),r(320),r(312),r(313),r(318),r(323),r(325),r(308),r(309),r(310),r(311),r(314),r(315),r(316),r(319),r(321),r(324),r(326),r(327),r(328),r(231),r(233),r(232),r(235),r(234),r(220),r(218),r(224),r(221),r(227),r(229),r(217),r(223),r(214),r(228),r(212),r(226),r(225),r(219),r(222),r(211),r(213),r(216),r(215),r(230),r(155),r(302),r(307),r(184),r(303),r(304),r(305),r(306),r(287),r(183),r(185),r(186),r(342),r(331),r(332),r(337),r(340),r(341),r(335),r(338),r(336),r(339),r(333),r(334),r(288),r(289),r(290),r(291),r(292),r(295),r(293),r(294),r(296),r(297),r(298),r(299),r(301),r(300),r(343),r(369),r(372),r(371),r(373),r(374),r(370),r(375),r(376),r(354),r(357),r(353),r(351),r(352),r(355),r(356),r(346),r(368),r(377),r(345),r(347),r(349),r(348),r(350),r(359),r(360),r(362),r(361),r(364),r(363),r(365),r(366),r(367),r(344),r(358),r(380),r(379),r(378),t.exports=r(52)},function(t,n){function r(t,n){if("string"==typeof n)return t.insertAdjacentHTML("afterend",n);var r=t.nextSibling;return r?t.parentNode.insertBefore(n,r):t.parentNode.appendChild(n)}t.exports=r},,,,,,,,,function(t,n,r){(function(n,r){!function(n){"use strict";function e(t,n,r,e){var i=n&&n.prototype instanceof o?n:o,u=Object.create(i.prototype),c=new p(e||[]);return u._invoke=s(t,r,c),u}function i(t,n,r){try{return{type:"normal",arg:t.call(n,r)}}catch(t){return{type:"throw",arg:t}}}function o(){}function u(){}function c(){}function f(t){["next","throw","return"].forEach(function(n){t[n]=function(t){return this._invoke(n,t)}})}function a(t){function n(r,e,o,u){var c=i(t[r],t,e);if("throw"!==c.type){var f=c.arg,a=f.value;return a&&"object"==typeof a&&m.call(a,"__await")?Promise.resolve(a.__await).then(function(t){n("next",t,o,u)},function(t){n("throw",t,o,u)}):Promise.resolve(a).then(function(t){f.value=t,o(f)},u)}u(c.arg)}function e(t,r){function e(){return new Promise(function(e,i){n(t,r,e,i)})}return o=o?o.then(e,e):e()}"object"==typeof r&&r.domain&&(n=r.domain.bind(n));var o;this._invoke=e}function s(t,n,r){var e=P;return function(o,u){if(e===F)throw new Error("Generator is already running");if(e===M){if("throw"===o)throw u;return y()}for(r.method=o,r.arg=u;;){var c=r.delegate;if(c){var f=l(c,r);if(f){if(f===A)continue;return f}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(e===P)throw e=M,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);e=F;var a=i(t,n,r);if("normal"===a.type){if(e=r.done?M:j,a.arg===A)continue;return{value:a.arg,done:r.done}}"throw"===a.type&&(e=M,r.method="throw",r.arg=a.arg)}}}function l(t,n){var r=t.iterator[n.method];if(r===g){if(n.delegate=null,"throw"===n.method){if(t.iterator.return&&(n.method="return",n.arg=g,l(t,n),"throw"===n.method))return A;n.method="throw",n.arg=new TypeError("The iterator does not provide a 'throw' method")}return A}var e=i(r,t.iterator,n.arg);if("throw"===e.type)return n.method="throw",n.arg=e.arg,n.delegate=null,A;var o=e.arg;return o?o.done?(n[t.resultName]=o.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=g),n.delegate=null,A):o:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,A)}function h(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.finallyLoc=t[2],n.afterLoc=t[3]),this.tryEntries.push(n)}function v(t){var n=t.completion||{};n.type="normal",delete n.arg,t.completion=n}function p(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(h,this),this.reset(!0)}function d(t){if(t){var n=t[w];if(n)return n.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var r=-1,e=function n(){for(;++r<t.length;)if(m.call(t,r))return n.value=t[r],n.done=!1,n;return n.value=g,n.done=!0,n};return e.next=e}}return{next:y}}function y(){return{value:g,done:!0}}var g,b=Object.prototype,m=b.hasOwnProperty,x="function"==typeof Symbol?Symbol:{},w=x.iterator||"@@iterator",S=x.asyncIterator||"@@asyncIterator",_=x.toStringTag||"@@toStringTag",O="object"==typeof t,E=n.regeneratorRuntime;if(E)return void(O&&(t.exports=E));E=n.regeneratorRuntime=O?t.exports:{},E.wrap=e;var P="suspendedStart",j="suspendedYield",F="executing",M="completed",A={},N={};N[w]=function(){return this};var T=Object.getPrototypeOf,I=T&&T(T(d([])));I&&I!==b&&m.call(I,w)&&(N=I);var k=c.prototype=o.prototype=Object.create(N);u.prototype=k.constructor=c,c.constructor=u,c[_]=u.displayName="GeneratorFunction",E.isGeneratorFunction=function(t){var n="function"==typeof t&&t.constructor;return!!n&&(n===u||"GeneratorFunction"===(n.displayName||n.name))},E.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,c):(t.__proto__=c,_ in t||(t[_]="GeneratorFunction")),t.prototype=Object.create(k),t},E.awrap=function(t){return{__await:t}},f(a.prototype),a.prototype[S]=function(){return this},E.AsyncIterator=a,E.async=function(t,n,r,i){var o=new a(e(t,n,r,i));return E.isGeneratorFunction(n)?o:o.next().then(function(t){return t.done?t.value:o.next()})},f(k),k[_]="Generator",k.toString=function(){return"[object Generator]"},E.keys=function(t){var n=[];for(var r in t)n.push(r);return n.reverse(),function r(){for(;n.length;){var e=n.pop();if(e in t)return r.value=e,r.done=!1,r}return r.done=!0,r}},E.values=d,p.prototype={constructor:p,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=g,this.done=!1,this.delegate=null,this.method="next",this.arg=g,this.tryEntries.forEach(v),!t)for(var n in this)"t"===n.charAt(0)&&m.call(this,n)&&!isNaN(+n.slice(1))&&(this[n]=g)},stop:function(){this.done=!0;var t=this.tryEntries[0],n=t.completion;if("throw"===n.type)throw n.arg;return this.rval},dispatchException:function(t){function n(n,e){return o.type="throw",o.arg=t,r.next=n,e&&(r.method="next",r.arg=g),!!e}if(this.done)throw t;for(var r=this,e=this.tryEntries.length-1;e>=0;--e){var i=this.tryEntries[e],o=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var u=m.call(i,"catchLoc"),c=m.call(i,"finallyLoc");if(u&&c){if(this.prev<i.catchLoc)return n(i.catchLoc,!0);if(this.prev<i.finallyLoc)return n(i.finallyLoc)}else if(u){if(this.prev<i.catchLoc)return n(i.catchLoc,!0)}else{if(!c)throw new Error("try statement without catch or finally");if(this.prev<i.finallyLoc)return n(i.finallyLoc)}}}},abrupt:function(t,n){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e.tryLoc<=this.prev&&m.call(e,"finallyLoc")&&this.prev<e.finallyLoc){var i=e;break}}i&&("break"===t||"continue"===t)&&i.tryLoc<=n&&n<=i.finallyLoc&&(i=null);var o=i?i.completion:{};return o.type=t,o.arg=n,i?(this.method="next",this.next=i.finallyLoc,A):this.complete(o)},complete:function(t,n){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&n&&(this.next=n),A},finish:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),v(r),A}},catch:function(t){for(var n=this.tryEntries.length-1;n>=0;--n){var r=this.tryEntries[n];if(r.tryLoc===t){var e=r.completion;if("throw"===e.type){var i=e.arg;v(r)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(t,n,r){return this.delegate={iterator:d(t),resultName:n,nextLoc:r},"next"===this.method&&(this.arg=g),A}}}("object"==typeof n?n:"object"==typeof window?window:"object"==typeof self?self:this)}).call(n,function(){return this}(),r(158))}])</script><script src="/./main.0cf68a.js"></script><script>!function(){!function(e){var t=document.createElement("script");document.getElementsByTagName("body")[0].appendChild(t),t.setAttribute("src",e)}("/slider.e37972.js")}()</script>


    
<div class="tools-col" q-class="show:isShow,hide:isShow|isFalse" q-on="click:stop(e)">
  <div class="tools-nav header-menu">
    
    
      
      
      
    
      
      
      
    
      
      
      
    
    

    <ul style="width: 70%">
    
    
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'innerArchive')"><a href="javascript:void(0)" q-class="active:innerArchive">所有文章</a></li>
      
        
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'friends')"><a href="javascript:void(0)" q-class="active:friends">友链</a></li>
      
        
      
      <li style="width: 33.333333333333336%" q-on="click: openSlider(e, 'aboutme')"><a href="javascript:void(0)" q-class="active:aboutme">关于我</a></li>
      
        
    </ul>
  </div>
  <div class="tools-wrap">
    
    	<section class="tools-section tools-section-all" q-show="innerArchive">
        <div class="search-wrap">
          <input class="search-ipt" q-model="search" type="text" placeholder="find something…">
          <i class="icon-search icon" q-show="search|isEmptyStr"></i>
          <i class="icon-close icon" q-show="search|isNotEmptyStr" q-on="click:clearChose(e)"></i>
        </div>
        <div class="widget tagcloud search-tag">
          <p class="search-tag-wording">tag:</p>
          <label class="search-switch">
            <input type="checkbox" q-on="click:toggleTag(e)" q-attr="checked:showTags">
          </label>
          <ul class="article-tag-list" q-show="showTags">
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color3">Android</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color3">Handler</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color5">bluetooth</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color5">Java</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color2">Kotlin</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color1">javascript</a>
              </li>
             
              <li class="article-tag-list-item">
                <a href="javascript:void(0)" class="js-tag color1">JavaScript</a>
              </li>
            
            <div class="clearfix"></div>
          </ul>
        </div>
        <ul class="search-ul">
          <p q-show="jsonFail" style="padding: 20px; font-size: 12px;">
            缺失模块。<br/>1、请确保node版本大于6.2<br/>2、在博客根目录（注意不是yilia根目录）执行以下命令：<br/> npm i hexo-generator-json-content --save<br/><br/>
            3、在根目录_config.yml里添加配置：
<pre style="font-size: 12px;" q-show="jsonFail">
  jsonContent:
    meta: false
    pages: false
    posts:
      title: true
      date: true
      path: true
      text: false
      raw: false
      content: false
      slug: false
      updated: false
      comments: false
      link: false
      permalink: false
      excerpt: false
      categories: false
      tags: true
</pre>
          </p>
          <li class="search-li" q-repeat="items" q-show="isShow">
            <a q-attr="href:path|urlformat" class="search-title"><i class="icon-quo-left icon"></i><span q-text="title"></span></a>
            <p class="search-time">
              <i class="icon-calendar icon"></i>
              <span q-text="date|dateformat"></span>
            </p>
            <p class="search-tag">
              <i class="icon-price-tags icon"></i>
              <span q-repeat="tags" q-on="click:choseTag(e, name)" q-text="name|tagformat"></span>
            </p>
          </li>
        </ul>
    	</section>
    

    
    	<section class="tools-section tools-section-friends" q-show="friends">
  		
        <ul class="search-ul">
          
            <li class="search-li">
              <a href="http://www.baidu.com/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>百度</a>
            </li>
          
            <li class="search-li">
              <a href="http://www.google.com/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>谷歌</a>
            </li>
          
            <li class="search-li">
              <a href="http://www.qq.com/" target="_blank" class="search-title"><i class="icon-quo-left icon"></i>腾讯</a>
            </li>
          
        </ul>
  		
    	</section>
    

    
    	<section class="tools-section tools-section-me" q-show="aboutme">
  	  	
  	  		<div class="aboutme-wrap" id="js-aboutme">很惭愧&lt;br&gt;&lt;br&gt;只做了一点微小的工作&lt;br&gt;谢谢大家</div>
  	  	
    	</section>
    
  </div>
  
</div>
    <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                      <div class="pswp__preloader__cut">
                        <div class="pswp__preloader__donut"></div>
                      </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div> 
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>
  </div>
</body>
</html>